グローバル変数は、2 つの異なるコアで同時に実行されている 2 つのスレッド間で共有されます。スレッドは変数に書き込み、変数から読み取ります。アトミック変数の場合、1 つのスレッドが古い値を読み取ることはできますか? 各コアは共有変数の値をキャッシュ内に保持している可能性があり、1 つのスレッドがキャッシュ内のコピーに書き込むと、別のコアの他のスレッドが独自のキャッシュから古い値を読み取る可能性があります。または、コンパイラは、他のキャッシュから最新の値を読み取り、強力なメモリ順序付けを実行しますか? c++11 標準ライブラリには、std::atomic のサポートがあります。これは、volatile キーワードとどう違うのですか? 上記のシナリオでは、volatile 型とアトミック型はどのように異なる動作をするのでしょうか?
ベストアンサー1
まず、volatile
はアトミック アクセスを意味するものではありません。 は、メモリ マップ I/O やシグナル処理などのために設計されています。volatile
は と一緒に使用する場合まったく不要でありstd::atomic
、プラットフォームで別途文書化されていない限り、volatile
スレッド間のアトミック アクセスやメモリ順序付けには影響しません。
次のような、スレッド間で共有されるグローバル変数がある場合:
std::atomic<int> ai;
可視性と順序付けの制約は、操作に使用するメモリ順序付けパラメータ、およびロック、スレッド、その他のアトミック変数へのアクセスの同期効果によって決まります。
追加の同期がない場合、1 つのスレッドが値を書き込んだ場合、ai
別のスレッドが特定の期間内にその値を参照できるという保証はありません。標準では、値が「妥当な期間内」に表示される必要があると規定されていますが、特定のアクセスでは古い値が返される可能性があります。
のデフォルトのメモリ順序は、すべての変数にわたるstd::memory_order_seq_cst
すべての操作に対して単一のグローバルな全体順序を提供しますstd::memory_order_seq_cst
。これは、古い値を取得できないという意味ではなく、取得する値によって、操作がこの全体順序のどこに位置するかが決まることを意味します。
x
2 つの共有変数とがありy
、初期値は 0 で、1 つのスレッドが に 1 を書き込みx
、別のスレッドが に 2 を書き込むy
場合、操作間に順序の制約がないため、両方を読み取る 3 番目のスレッドは (0,0)、(1,0)、(0,2)、または (1,2) のいずれかを参照する可能性があります。そのため、操作はグローバル順序で任意の順序で出現する可能性があります。
両方の書き込みが同じスレッドからのものであり、そのx=1
前に書き込みを行いy=2
、読み取りスレッドがy
前に読み取りを行うx
場合、(0,2) は有効なオプションではなくなります。これは、読み取りによって、y==2
以前の書き込みがx
可視であることが暗示されるためです。他の 3 つのペア (0,0)、(1,0)、(1,2) は、2 つの読み取りが 2 つの書き込みとどのようにインターリーブされるかに応じて、引き続き可能です。
std::memory_order_relaxed
またはなどの他のメモリ順序付けを使用する場合、std::memory_order_acquire
制約はさらに緩和され、単一のグローバル順序付けは適用されなくなります。追加の同期がない場合、スレッドは、別々の変数への 2 つのストアの順序付けに必ずしも同意する必要さえありません。
「最新」の値を確実に取得する唯一の方法は、、またはなどexchange()
の読み取り-変更-書き込み操作を使用することです。読み取り-変更-書き込み操作には、常に「最新」の値に対して操作するという追加の制約があるため、一連のスレッドによる一連の操作では、重複やギャップのない値のシーケンスが返されます。追加の制約がない場合でも、どのスレッドがどの値を表示するかは保証されません。特に、RMW操作の使用によって他のスレッドからの変更がより速く表示されるようになるわけではないことに注意してください。変更がcompare_exchange_strong()
fetch_add()
ai.fetch_add(1)
ないRMW によって参照される場合、すべてのスレッドは、そのアトミック変数の変更順序が RMW 操作よりも後であることに同意する必要があります。異なるスレッドからのストアは、CPU がいつ実行されるかに応じて、任意の時間だけ遅延される可能性があります。実はストアをメモリに発行する(独自のストアバッファではなく)物理的にスレッドを実行する CPU 間の距離 (マルチプロセッサ システムの場合) と、キャッシュ一貫性プロトコルの詳細。
アトミック操作の扱いは複雑なトピックです。アトミックを使用して製品コードを書く前に、多くの背景資料を読み、公開されているコードを調べることをお勧めします。ほとんどの場合、ロックを使用するコードを書く方が簡単で、効率が著しく低下することはありません。