アトミック命令と変数更新の可視性
-
06-07-2019 - |
質問
ほとんどの一般的なプラットフォーム(最も重要なのはx86です。一部のプラットフォームでは、マルチスレッドに役立つ保証がほとんどない非常に難しいメモリモデルがあることを理解していますが、まれな反例は気にしません)安全ですか?
スレッド1:
someVariable = doStuff();
atomicSet(stuffDoneFlag, 1);
スレッド2:
while(!atomicRead(stuffDoneFlag)) {} // Wait for stuffDoneFlag to be set.
doMoreStuff(someVariable);
アトミックopsの標準的で合理的な実装を想定:
-
atomicSet()
が呼び出される前に、スレッド1のsomeVariable
への割り当てが完了することが保証されていますか? -
stuffDoneFlag
をアトミックに読み取る場合、スレッド2はdoMoreStuff()
を呼び出す前にsomeVariable
への割り当てを確認することが保証されていますか?
編集:
- 使用しているアトミックopsの実装には、それぞれにx86
LOCK
命令が含まれています 操作が役立つ場合。 -
stuffDoneFlag
が何らかの形で適切にクリアされていると仮定します。どのように重要ではありません。 - これは非常に単純化された例です。この方法で作成したので、回答するために問題のコンテキスト全体を理解する必要はありません。効率的ではないことを知っています。
解決
実際のx86コードに、スレッド1のatomicSetのストアの前にsomeVariableへのストアがあり、スレッド2のatomicReadのロード後にsomeVariableのロードがある場合、問題ありません。 Intelのソフトウェア開発者向けマニュアル第3巻は、セクション8.2でx86のメモリモデルを指定しています。ここでは、スレッド内のstore-storeおよびload-loadの制約で十分です。
ただし、アトミック操作全体で使用しているより高いレベルの言語から生成された命令をコンパイラーが並べ替えることを妨げるものはないかもしれません。
他のヒント
1)はい
2)はい
両方の作業。
このコードはスレッドセーフに見えますが、 spinlock (whileループ)非常に短い時間だけ回転している場合を除きます。特定のシステムでは、スレッド2がすべての処理時間を完全に消費しないという保証はありません。
実際の同期プリミティブを使用することをお勧めします( boost :: condition_variable はここで必要なものです)スピンロックに依存する代わりに。
アトミック命令により、スレッド2は、スレッド1が変数の設定を完了するのを待ってから、スレッド2に進みます。ただし、2つの重要な問題があります。
1) someVariable
は「 volatile 'を使用して、コンパイラが割り当てを最適化しないようにします。レジスタに保存するか、書き込みを延期します。
2)2番目のスレッドは、シグナルを待っている間ブロックしています(スピンロックと呼ばれます)。あなたのプラットフォームはおそらくはるかに優れたロックとシグナリングのプリミティブとメカニズムを提供しますが、比較的簡単な改善は、スレッド2の while()
本体で単に sleep()
することです。 p>
dsimcha written:" stuffDoneFlagが何らかの形で適切にクリアされると仮定します。どのように重要ではありません。" これは真実ではありません!
シナリオを見てみましょう:
- Thread2は、someVariableの読み取りを開始した場合、stuffDoneFlagをチェックします。
- Thread2がタスクスケジューラの読み取りを完了する前に、タスクを中断し、タスクをしばらく中断します。
- Thread1は再びsomeVariableにアクセスし、メモリの内容を変更します。
- タスクスケジューラは再びThread2をオンにし、ジョブを続行しますが、someVariableのメモリコンテンツが変更されます。