memory_order
| ヘッダ <stdatomic.h>で定義 |
||
| enum memory_order { |
(C11 以降) | |
memory_order は、通常の非アトミックなメモリアクセスを含むメモリ操作が、アトミック操作の前後でどのように順序付けられるかを指定します。マルチコアシステムで制約がない場合、複数のスレッドが同時に複数の変数に対して読み書きを行うと、あるスレッドが値を変更する順序は、別のスレッドが書き込んだ順序とは異なる可能性があります。実際、変更の見える順序は、複数の読み取りスレッド間でさえ異なることがあります。メモリモデルによって許可されるコンパイラの変換により、単一プロセッサシステムでも同様の効果が発生する可能性があります。
言語とライブラリのすべてのアトミック操作のデフォルトの動作は、順序整合性順序付け(下記参照)を提供します。このデフォルトはパフォーマンスを低下させる可能性がありますが、ライブラリのアトミック操作には追加の memory_order 引数を指定して、アトミック性以外にコンパイラとプロセッサがその操作に対して強制する必要がある正確な制約を指定できます。
目次 |
[edit] 定数
| ヘッダ
<stdatomic.h>で定義 | |
| 値 | 説明 |
memory_order_relaxed
|
Relaxed operation (緩和された操作): 他の読み取りまたは書き込みに対して同期または順序付けの制約は課されません。この操作のアトミック性のみが保証されます(下記 Relaxed ordering 参照)。 |
memory_order_consume(C++26で非推奨) |
このメモリ順序を持つロード操作は、影響を受けるメモリ位置に対してコンシューム操作を実行します。現在のスレッドで、現在ロードされている値に依存する読み取りまたは書き込みは、このロードより前に再順序付けされることはありません。同じアトミック変数をリリースする他のスレッドからのデータ依存変数への書き込みは、現在のスレッドで可視になります。ほとんどのプラットフォームでは、これはコンパイラの最適化にのみ影響します(下記 Release-Consume ordering 参照)。 |
memory_order_acquire
|
このメモリ順序を持つロード操作は、影響を受けるメモリ位置に対してアキュア操作を実行します。現在のスレッドで、このロードより前に再順序付けされる読み取りまたは書き込みはありません。同じアトミック変数をリリースする他のスレッドからのすべての書き込みは、現在のスレッドで可視になります(下記 Release-Acquire ordering 参照)。 |
memory_order_release
|
このメモリ順序を持つストア操作は、リリース操作を実行します。現在のスレッドで、このストアより後に再順序付けされる読み取りまたは書き込みはありません。現在のスレッドからのすべての書き込みは、同じアトミック変数をアキュアする他のスレッドで可視になります(下記 Release-Acquire ordering 参照)。また、アトミック変数に依存関係を運ぶ書き込みは、同じアトミック変数をコンシュームする他のスレッドで可視になります(下記 Release-Consume ordering 参照)。 |
memory_order_acq_rel
|
このメモリ順序を持つ読み取り-変更-書き込み操作は、アキュア操作とリリース操作の両方です。現在のスレッドで、このロードより前に再順序付けされるメモリ読み取りまたは書き込みはなく、またこのストアより後に再順序付けされるメモリ読み取りまたは書き込みもありません。同じアトミック変数をリリースする他のスレッドからのすべての書き込みは、変更より前に可視であり、変更は同じアトミック変数をアキュアする他のスレッドで可視になります。 |
memory_order_seq_cst
|
このメモリ順序を持つロード操作はアキュア操作を実行し、ストアはリリース操作を実行し、読み取り-変更-書き込みはアキュア操作とリリース操作の両方を実行します。さらに、すべてのスレッドがすべての変更を同じ順序で観測する単一の総順序が存在します(下記 Sequentially-consistent ordering 参照)。 |
| このセクションは未完成です 理由: happens-beforeなどの概念は、C++と同様ですが、C11の変更順序と4つの整合性は c/language/atomic で維持します。 |
| このセクションは未完成です 理由: 上記を行う際、C11のpublished版ではhappens-beforeは非循環的でしたが、DR 401によりC++11と一致するように更新されたことを忘れないでください。 |
[edit] Relaxed ordering (緩和された順序付け)
memory_order_relaxed タグ付けされたアトミック操作は、同期操作ではありません。これらは、並行メモリアクセス間に順序付けを課しません。アトミック性と変更順序の一貫性のみを保証します。
例えば、x と y が初期値ゼロの場合、
// Thread 1:
r1 = atomic_load_explicit(y, memory_order_relaxed); // A
atomic_store_explicit(x, r1, memory_order_relaxed); // B
// Thread 2:
r2 = atomic_load_explicit(x, memory_order_relaxed); // C
atomic_store_explicit(y, 42, memory_order_relaxed); // D は、スレッド1内ではAがBより順序付けられて先行し、スレッド2内ではCがDより順序付けられて先行しますが、Dがyの変更順序でAより先に現れること、およびBがxの変更順序でCより先に現れることを妨げるものがないため、r1 == r2 == 42 を生成することが許可されます。Dのyへの副作用は、スレッド1のロードAで可視になる可能性があり、Bのxへの副作用は、スレッド2のロードCで可視になる可能性があります。特に、Dがスレッド2でCより先に完了する場合、コンパイラの再順序付けまたは実行時によって発生する可能性があります。
緩和されたメモリ順序の典型的な使用例は、カウンタのインクリメント、例えば参照カウンタなどです。これはアトミック性のみを必要とし、順序付けや同期を必要としないためです。
[edit] Release-Consume ordering (リリース-コンシューム順序付け)
スレッドAでのアトミックストアがmemory_order_releaseでタグ付けされ、スレッドBでの同じ変数に対するアトミックロードがmemory_order_consumeでタグ付けされ、スレッドBでのロードがスレッドAでのストアによって書き込まれた値を読み取る場合、スレッドAでのストアはスレッドBでのロードより依存関係順序付けされて先行します。
スレッドAの観点からアトミックストアより先行していたすべてのメモリ書き込み(非アトミックおよび緩和されたアトミック)は、ロード操作が依存関係を運ぶスレッドBでの操作において可視な副作用になります。つまり、アトミックロードが完了すると、ロードから得られた値を使用するスレッドBのオペレータと関数は、スレッドAがメモリに書き込んだものを必ず見ることが保証されます。
同期は、同じアトミック変数をリリースおよびコンシュームするスレッド間でのみ確立されます。他のスレッドは、同期されたスレッドのいずれか、または両方とは異なるメモリアクセス順序を見ることがあります。
DEC Alpha以外のすべての主要CPUでは、依存関係順序付けは自動的に行われます。この同期モードに追加のCPU命令は発行されません。コンパイラの最適化の一部のみが影響を受けます(例: コンパイラは、依存関係チェーンに関与するオブジェクトに対する投機的なロードを実行することが禁止されます)。
この順序付けの典型的な使用例としては、めったに書き込まれない並行データ構造(ルーティングテーブル、設定、セキュリティポリシー、ファイアウォールルールなど)の読み取りアクセスや、ポインタを介した公開を伴うパブリッシャー/サブスクライバーの状況(つまり、プロデューサーがコンシューマーが情報にアクセスできるポインタを公開する場合)があります。プロデューサーがメモリに書き込んだ他のすべてのものをコンシューマー(弱順序付けアーキテクチャでは高コストな操作になる可能性がある)に可視にする必要はありません。そのようなシナリオの例はrcu_dereferenceです。
現在(2015年2月)、生産コンパイラは依存関係チェーンを追跡していません。コンシューム操作はアキュア操作に引き上げられます。
[edit] Release sequence (リリースシーケンス)
あるアトミックがストア-リリースされ、複数の他のスレッドがそのアトミックに対して読み取り-変更-書き込み操作を実行する場合、「リリースシーケンス」が形成されます。同じアトミックに対して読み取り-変更-書き込みを実行するすべてのスレッドは、たとえmemory_order_releaseセマンティクスを持っていなくても、最初のスレッドおよび互いに同期します。これにより、個々のコンシューマースレッド間に不要な同期を課すことなく、シングルプロデューサー-マルチコンシューマーの状況が可能になります。
[edit] Release-Acquire ordering (リリース-アキュア順序付け)
スレッドAでのアトミックストアがmemory_order_releaseでタグ付けされ、スレッドBでの同じ変数に対するアトミックロードがmemory_order_acquireでタグ付けされ、スレッドBでのロードがスレッドAでのストアによって書き込まれた値を読み取る場合、スレッドAでのストアはスレッドBでのロードと同期します。
スレッドAの観点からアトミックストアより先行していたすべてのメモリ書き込み(非アトミックおよび緩和されたアトミックを含む)は、スレッドBでの可視な副作用になります。つまり、アトミックロードが完了すると、スレッドBはスレッドAがメモリに書き込んだすべてを必ず見ることが保証されます。この約束は、Bが実際にAが格納した値、またはリリースシーケンスの後の値を受け取った場合にのみ有効です。
同期は、同じアトミック変数をリリースおよびアキュアするスレッド間でのみ確立されます。他のスレッドは、同期されたスレッドのいずれか、または両方とは異なるメモリアクセス順序を見ることがあります。
強く順序付けられたシステム(x86、SPARC TSO、IBMメインフレームなど)では、リリース-アキュア順序付けはほとんどの操作で自動的に行われます。この同期モードに追加のCPU命令は発行されません。コンパイラの最適化の一部のみが影響を受けます(例: コンパイラは、非アトミックストアをアトミックストア-リリースより先に移動したり、非アトミックロードをアトミックロード-アキュアより前に実行したりすることが禁止されます)。弱順序付けシステム(ARM、Itanium、PowerPC)では、特別なCPUロードまたはメモリフェンス命令が使用されます。
ミューテックスやアトミックスピンロックなどの相互排他ロックは、リリース-アキュア同期の例です。ロックがスレッドAによって解放され、スレッドBによって取得されると、クリティカルセクション(リリース前)でスレッドAのコンテキストで発生したすべてが、同じクリティカルセクションを実行しているスレッドB(アキュア後)に可視であることが必要です。
[edit] Sequentially-consistent ordering (順序整合性順序付け)
memory_order_seq_cstでタグ付けされたアトミック操作は、メモリをリリース/アキュア順序付けと同じように順序付けるだけでなく(あるスレッドでのストアより先行していたすべてが、ロードを行ったスレッドで可視な副作用になる)、タグ付けされたすべてのアトミック操作の単一の総変更順序を確立します。
正式には、
Mからロードする各memory_order_seq_cst操作Bは、以下のいずれかを観測します。
- 単一の総順序でBより前に現れるMを最後に変更した操作Aの結果、
- または、そのようなAが存在した場合、BはMの
memory_order_seq_cstではなく、Aより先行しない変更の結果を観測する可能性があります。 - または、そのようなAが存在しない場合、BはMの無関係な変更の結果を観測する可能性があります。
Mに対するmemory_order_seq_cst atomic_thread_fence操作XがBより順序付けられて先行していた場合、Bは以下のいずれかを観測します。
- 単一の総順序でXより前に現れるMの最後の
memory_order_seq_cst変更、 - Mの変更順序でより後に現れる無関係な変更。
Mに対するアトミック操作のペアAとBがあり、Aが書き込み、BがMの値を読み取るとします。もし2つのmemory_order_seq_cst atomic_thread_fence XとYがあり、AがXより順序付けられて先行し、YがBより順序付けられて先行し、Xが単一の総順序でYより前に現れる場合、Bは以下を観測します。
- Aの効果、
- Mの変更順序でAより後に現れる無関係な変更。
Mの2つのアトミック変更AとBについて、BがMの変更順序でAより後に現れるのは、以下のいずれかの場合です。
memory_order_seq_cstatomic_thread_fence Xが存在し、AがXより順序付けられて先行し、Xが単一の総順序でBより前に現れる場合。- または、
memory_order_seq_cstatomic_thread_fence Yが存在し、YがBより順序付けられて先行し、Aが単一の総順序でYより前に現れる場合。 - または、
memory_order_seq_cstatomic_thread_fence XとYが存在し、AがXより順序付けられて先行し、YがBより順序付けられて先行し、Xが単一の総順序でYより前に現れる場合。
これは、以下のことを意味します。
memory_order_seq_cstでタグ付けされていないアトミック操作が関与すると、順序整合性は失われます。順序整合性は、すべてのコンシューマーがすべてのプロデューサーのアクションを同じ順序で観測する必要がある、複数のプロデューサー-複数のコンシューマーの状況で必要になる場合があります。
総順序整合性には、すべてのマルチコアシステムで完全なメモリフェンスCPU命令が必要です。これは、影響を受けるメモリアクセスをすべてのコアに伝播させるため、パフォーマンスのボトルネックになる可能性があります。
[edit] Relationship with volatile (volatileとの関係)
実行スレッド内では、volatile lvaluesを介したアクセス(読み取りと書き込み)は、同じスレッド内のシーケンスポイントで区切られた観測可能な副作用(他のvolatileアクセスを含む)より先に再順序付けされることはありません。ただし、volatileアクセスはスレッド間の同期を確立しないため、この順序は他のスレッドによって観測されることが保証されていません。
さらに、volatileアクセスはアトミックではなく(同時読み取りと書き込みはデータ競合です)、メモリを順序付けません(非volatileメモリアクセスは、volatileアクセスを中心に自由に再順序付けされる可能性があります)。
1つの注目すべき例外はVisual Studioです。デフォルト設定では、すべてのvolatile書き込みはリリースセマンティクスを持ち、すべてのvolatile読み取りはアキュアセマンティクスを持ちます(Microsoft Docs)。したがって、volatileはスレッド間同期に使用できます。標準のvolatileセマンティクスはマルチスレッドプログラミングには適用されませんが、同じスレッドで実行されるsig_atomic_t変数に適用される場合、signalハンドラとの通信などには十分です。
[edit] 例
| このセクションは未完成です 理由: 例がありません |
[edit] 参考文献
- C23標準 (ISO/IEC 9899:2024)
- 7.17.1/4 memory_order (p: TBD)
- 7.17.3 Order and consistency (p: TBD)
- C17標準 (ISO/IEC 9899:2018)
- 7.17.1/4 memory_order (p: 200)
- 7.17.3 Order and consistency (p: 201-203)
- C11標準 (ISO/IEC 9899:2011)
- 7.17.1/4 memory_order (p: 273)
- 7.17.3 Order and consistency (p: 275-277)
[edit] 関連項目
| C++ドキュメント for memory order
|
[edit] 外部リンク
| 1. | MOESI protocol |
| 2. | x86-TSO: A Rigorous and Usable Programmer’s Model for x86 Multiprocessors P. Sewell et. al., 2010 |
| 3. | A Tutorial Introduction to the ARM and POWER Relaxed Memory Models P. Sewell et al, 2012 |
| 4. | MESIF: A Two-Hop Cache Coherency Protocol for Point-to-Point Interconnects J.R. Goodman, H.H.J. Hum, 2009 |
| 5. | Memory Models Russ Cox, 2021 |
| このセクションは未完成です 理由: QPI、MOESI、そしておそらくDragonの良い参考文献を見つけましょう。 |