3

更新サイクルでメインスレッドからgetterを継続的に呼び出し、別のスレッドからsetterを呼び出すときに、ミューテックスを使用して変数をロックおよびロック解除します。セッターとゲッターのコードを以下に示しました

意味

bool _flag;
System::Mutex m_flag;

呼び出し

#define LOCK(MUTEX_VAR) MUTEX_VAR.Lock();
#define UNLOCK(MUTEX_VAR) MUTEX_VAR.Unlock();

void LoadingScreen::SetFlag(bool value)
{
    LOCK(m_flag);
    _flag = value;
    UNLOCK(m_flag);
}

bool LoadingScreen::GetFlag()
{
    LOCK(m_flag);
    bool value = _flag;
    UNLOCK(m_flag);

    return value;
}

これは半分の時間でうまく機能しますが、SetFlagの呼び出し時に変数がロックされることがあるため、設定されないため、コードのフローが妨げられます。

誰かがこの問題を解決する方法を教えてもらえますか?

編集:

これは私が最終的に行った回避策です。これは一時的な解決策にすぎません。誰かがより良い答えを持っているなら、私に知らせてください。

bool _flag;
bool accessingFlag = false;

void LoadingScreen::SetFlag(bool value)
{
    if(!accessingFlag)
    {
        _flag = value;
    }
}

bool LoadingScreen::GetFlag()
{
    accessingFlag = true;
    bool value = _flag;
    accessingFlag = false;

    return value;
}
4

6 に答える 6

2

まず、ミューテックスのロック/ロック解除にRAIIを使用する必要があります。次に、_flagを直接使用する他のコードを表示しないか、使用しているミューテックスに問題があります(ありそうもない)。System :: Mutexを提供するライブラリは何ですか?

于 2013-02-27T16:20:30.427 に答える
2

あなたが抱えている問題(user1192878が暗示している)は、コンパイラのロード/ストアの遅延が原因です。コードを実装するには、メモリ バリアを使用する必要があります。を宣言することができますvolatile bool _flag;。ただし、これは 、シングル CPU システムのコンパイラ メモリ バリアでは必要ありません。マルチ CPU ソリューションには、ハードウェア バリア (ウィキペディアのリンクのすぐ下) が必要です。ハードウェア バリアにより、ローカル プロセッサのメモリ/キャッシュがすべての CPU から見えるようになります。mutexこの場合、および他のインターロックを使用する必要はありません。彼らは正確に何を達成しますか?それらはデッドロックを作成するだけであり、必要ありません。

bool _flag;
#define memory_barrier __asm__ __volatile__ ("" ::: "memory") /* GCC */

void LoadingScreen::SetFlag(bool value)
{
    _flag = value;
    memory_barrier(); /* Ensure write happens immediately, even for in-lines */
}

bool LoadingScreen::GetFlag()
{
   bool value = _flag;
   memory_barrier(); /* Ensure read happens immediately, even for in-lines */
   return value;
}

ミューテックスは、複数の値が同時に設定されている場合にのみ必要です。boolタイプをsig_atomic_tまたはLLVM atomics に変更することもできます。boolただし、これはほとんどすべての実際の CPU アーキテクチャで機能するため、かなり専門的です。Cocoa の同時実行ページには、同じことを行うための代替 API に関する情報もあります。gcc のインライン アセンブラは、Apple のコンパイラで使用されるものと同じ構文だと思います。しかし、それは間違っている可能性があります。

API にはいくつかの制限があります。インスタンスGetFlag()が返され、何かを呼び出すことができますSetFlag()GetFlag()戻り値は古くなります。複数のライターがいる場合、簡単に 1 つを見逃してしまいますSetFlag()これは、より高いレベルのロジックでABA の問題が発生しやすい場合に重要です。ただし、これらの問題はすべてミューテックスの有無にかかわらず存在します。メモリ バリアは、コンパイラ/CPU が を長時間キャッシュせず、 の値を再読み込みするという問題のみを解決します。宣言すると通常は同じ動作になりますが、追加の副作用があり、マルチ CPU の問題は解決しません。SetFlag()GetFlag()volatile bool flag

std::atomic<bool>stefanよるatomic_set(&accessing_flag, true);と、通常、実装で上記と同じことを行います。プラットフォームで利用できる場合は、それらを使用することをお勧めします。

于 2013-03-10T21:35:43.177 に答える
1

System::Mutex が正しく実装されている場合、コードは正しく見えます。言及すべきこと:

  1. 他の人が指摘したように、RAII はマクロより優れています。

  2. accessFlag と _flag を volatile として定義した方が良いかもしれません。

  3. 最適化してコンパイルすると、得られた一時的なソリューションは正しくないと思います。

    bool LoadingScreen::GetFlag()
    {
      accessFlag = true; // 並べ替えまたは削除される可能性があります
      ブール値 = _flag; // 最適化される可能性があります
      accessFlag = false; // 値が設定される前に並べ替えられる可能性があります
      戻り値; // _flag または register を直接返すように最適化される可能性があります
    }
    
    上記のコードでは、オプティマイザーが厄介なことを行う可能性があります。たとえば、コンパイラがaccessFlag=trueへの最初の割り当てを削除することを妨げるものは何もありません。たとえば、コンパイラの観点からすると、シングルスレッドの場合、値 true が使用されないため、accessingFlag への最初の割り当ては役に立ちません。

  4. 単一の bool 変数を保護するためにミューテックスを使用すると、ほとんどの時間が OS モードの切り替え (カーネルからユーザーへの切り替え) に費やされるため、費用がかかります。スピンロックを使用するのも悪くないかもしれません (詳細なコードはターゲット プラットフォームによって異なります)。次のようになります。

    spinlock_lock(&ロック); _フラグ = 値; spinlock_unlock(&lock);

  5. ここでもアトミック変数がいいですね。次のようになります。
atomic_set(&accessing_flag, true);
于 2013-03-10T17:17:45.330 に答える
0

指定したコードの2番目のブロックは、ユニプロセッサ設定であっても、読み取り中にフラグを変更する場合があります。投稿した元のコードは正しく、次の2つの仮定の下でデッドロックにつながることはありません。

  1. m_flagsロックは正しく初期化されており、他のコードによって変更されることはありません。
  2. ロックの実装は正しいです。

ポータブルロックの実装が必要な場合は、OpenMPの使用をお勧めします: openMPでロックを使用するにはどうすればよいですか?

あなたの説明から、スレッドが入力を処理するのを忙しく待ちたいようです。この場合、stefansソリューション(フラグstd :: atomicを宣言する)がおそらく最適です。セミセインx86システムでは、フラグvolatileintを宣言することもできます。整列されていないフィールド(パックされた構造)に対してはこれを行わないでください。

2つのロックでビジーウェイトを回避できます。最初のロックは、処理が終了するとスレーブによってロック解除され、スレーブが終了するのを待つときにメインスレッドによってロックされます。2番目のロックは、入力を提供するときにメインスレッドによってロック解除され、入力を待機するときにスレーブによってロックされます。

于 2013-03-10T16:17:41.613 に答える
0

CRITICAL_SECTION の使用を検討しましたか? これは Windows でのみ使用できるため、移植性がいくらか失われますが、効果的なユーザー レベルのミューテックスです。

于 2013-03-06T17:29:54.443 に答える
-1

これは私がどこかで見たテクニックですが、もうソースを見つけることができませんでした. 見つかったら、回答を編集します。基本的に、ライターは単に書き込むだけですが、リーダーはセット変数の値を複数回読み取り、すべてのコピーが一貫している場合にのみそれを使用します。そして、期待する値と一致しない限り値を書き続けようとするように、ライターを変更しました。

bool _flag;

void LoadingScreen::SetFlag(bool value)
{
    do
    {
       _flag = value;
    } while (_flag != value);
}

bool LoadingScreen::GetFlag()
{
    bool value;

    do
    {
        value = _flag;
    } while (value != _flag);

    return value;
}
于 2013-03-10T14:03:46.833 に答える