ストアはリリース操作であり、ロードは両方の取得操作です。 memory_order_seq_cst
は、すべての操作に追加の全順序付けを課すことを意図していることは知っていますが、すべてのmemory_order_seq_cst
がmemory_order_acq_rel
に置き換えられた場合に当てはまらない例を作成できていません。
何かが足りないのですか、それとも違いは単なるドキュメントの効果です。つまり、よりリラックスしたモデルで遊ぶつもりがない場合はmemory_order_seq_cst
を使用し、リラックスしたモデルを制約する場合はmemory_order_acq_rel
を使用する必要がありますか?
http://en.cppreference.com/w/cpp/atomic/memory_order 良い例があります 下部memory_order_seq_cst
でのみ機能します。基本的に、memory_order_acq_rel
はアトミック変数に関連する読み取りと書き込みの順序を提供し、memory_order_seq_cst
はグローバルに読み取りと書き込みの順序を提供します。つまり、順次一貫性のある操作は、すべてのスレッドで同じ順序で表示されます。
例はこれに要約されます:
bool x= false;
bool y= false;
int z= 0;
a() { x= true; }
b() { y= true; }
c() { while (!x); if (y) z++; }
d() { while (!y); if (x) z++; }
// kick off a, b, c, d, join all threads
assert(z!=0);
z
の操作は、1つではなく2つのアトミック変数によって保護されているため、acquire-releaseセマンティクスを使用して、z
が常にインクリメントされるようにすることはできません。
アトミックがバリアにマップされ、実際のマシンモデルにストアバッファが含まれているx86のようなISAでは、次のようになります。
seq_cst
ストアはストアバッファをフラッシュする必要があるため、このスレッドの後の読み取りは、ストアがグローバルに表示されるまで遅延されます。acq_rel
はnotストアバッファをフラッシュします。通常のx86のロードとストアには、基本的にacqとrelのセマンティクスがあります。 (seq_cstに加えて、ストア転送を備えたストアバッファー。)
ただし、x86 asm lock
プレフィックスは完全なメモリバリアであるため、x86アトミックRMW操作は常にseq_cst
にプロモートされます。他のISAは、asmでrelaxedまたはacq_relRMWを実行できます。
https://preshing.com/20120515/memory-reordering-caught-in-the-act は、seq_cstストアとaの違いの有益な例です。プレーンリリースストア。(実際には、x86asmのプレーンmov
に対してmfence
+ mov
です。実際にはxchg
ほとんどのx86CPUでseq_cstストアを実行するためのより効率的な方法ですが、GCCはmov
+ mfence
を使用します)
おもしろい事実:AArch64のSTLRリリースストア命令は実際にはsequential-releaseです。ハードウェアでは、relaxedまたはseq_cstを使用したロード/ストアと、フルバリア命令があります。
理論的には、STLRはストアバッファのドレインのみを必要とします次のLDARの前、他の操作の前ではありません。つまり、次のseq_cstロードの前です。実際のAArch64HWがこの方法で実装するのか、それともSTLRをコミットする前にストアバッファをドレインするだけなのかはわかりません。 (いずれの場合も、以前のすべてのストアはSTLRの前にコミットする必要がありますが、必ずしも後のプレーンロードの前にコミットする必要はありません。)
したがって、LDAR/STLRを使用してrelまたはacq_relをseq_cstに強化するのに費用がかかる必要はありません。
他の一部のISA(PowerPCなど)には、バリアの選択肢が多く、mo_rel
よりもmo_acq_rel
またはmo_seq_cst
まで安価に強化できますが、seq_cst
はそれほど安くはありません。 AArch64として; seq-cstストアには完全なバリアが必要です。
memory_order の定義と例を引き続き使用します。ただし、memory_order_seq_cstをstoreではmemory_order_releaseに、loadではmemory_order_acquireに置き換えてください。
リリース-取得順序は、発生したすべてを保証します-1つのスレッドのstoreが、ロードを行ったスレッドで目に見える副作用になる前に。しかし、この例では、thread0とthread1の両方でstoreの前には何も起こりません。
x.store(true, std::memory_order_release); // thread0
y.store(true, std::memory_order_release); // thread1
さらに、memory_order_seq_cstがないと、thread2とthread3の順番は保証されません。あなたはそれらが次のようになると想像することができます:
if (y.load(std::memory_order_acquire)) { ++z; } // thread2, load y first
while (!x.load(std::memory_order_acquire)); // and then, load x
if (x.load(std::memory_order_acquire)) { ++z; } // thread3, load x first
while (!y.load(std::memory_order_acquire)); // and then, load y
したがって、thread2とthread3がthread0とthread1の前に実行された場合、つまりxとyの両方がfalseのままであるため、++ zに触れることはなく、zは0のままであり、アサートが発生します。
ただし、memory_order_seq_cstが画像に入ると、そのようにタグ付けされたすべてのアトミック操作の単一の合計変更順序が確立されます。したがって、thread2では、x.load、次にy.loadです。 thread3では、y.load、次にx.loadが確実です。