Tがプリミティブ型である任意のstd::atomic<T>
の場合:
std::memory_order_acq_rel
操作にfetch_xxx
を使用し、load
操作にstd::memory_order_acquire
を使用し、store
操作にstd::memory_order_release
を盲目的に使用する場合(つまり、これらの関数のデフォルトのメモリ順序をリセットする)
std::memory_order_seq_cst
(デフォルトとして使用されている)を使用した場合と同じになりますか?std::memory_order_seq_cst
を使用する場合とはどういうわけか異なりますか?アトミック操作のC++ 11メモリ順序パラメータは、順序の制約を指定します。 std::memory_order_release
でストアを実行し、別のスレッドからのロードがstd::memory_order_acquire
で値を読み取る場合、2番目のスレッドからの後続の読み取り操作では、ストアのリリース前の最初のスレッドによって任意のメモリ位置に格納された値が表示されますまたは後でそれらのメモリ位置のいずれかに保存します。
ストアと後続のロードの両方がstd::memory_order_seq_cst
の場合、これら2つのスレッド間の関係は同じです。違いを確認するには、より多くのスレッドが必要です。
例えばstd::atomic<int>
変数x
およびy
、どちらも最初は0。
スレッド1:
x.store(1,std::memory_order_release);
スレッド2:
y.store(1,std::memory_order_release);
スレッド3:
int a=x.load(std::memory_order_acquire); // x before y
int b=y.load(std::memory_order_acquire);
スレッド4:
int c=y.load(std::memory_order_acquire); // y before x
int d=x.load(std::memory_order_acquire);
記述されているように、ストアとx
およびy
の間に関係はないため、スレッド3ではa==1
、b==0
、スレッド4ではc==1
およびd==0
を確認できます。
すべてのメモリ順序がstd::memory_order_seq_cst
に変更された場合、これにより、ストア間の順序がx
とy
に強制されます。したがって、スレッド3がa==1
とb==0
を検出した場合、それはx
へのストアがy
へのストアの前にある必要があることを意味します。したがって、スレッド4がc==1
を認識した場合、つまりy
へのストアが完了した場合、x
へのストアもd==1
が必要です。
実際には、どこでもstd::memory_order_seq_cst
を使用すると、コンパイラとプロセッサのアーキテクチャに応じて、ロードまたはストア、あるいはその両方にオーバーヘッドが追加されます。例えばx86プロセッサの一般的な手法は、必要な順序の保証を提供するために、std::memory_order_seq_cst
ストアにXCHG
命令ではなく、MOV
命令を使用することですが、std::memory_order_release
には、プレーンなMOV
で十分です。よりリラックスしたメモリアーキテクチャを備えたシステムでは、プレーンなロードとストアの保証が少ないため、オーバーヘッドが大きくなる可能性があります。
メモリオーダリングは難しいです。 私の本 で、ほぼすべての章をそれに捧げました。
メモリの順序付けは非常に難しい場合があり、間違った場合の影響は非常に微妙なことがよくあります。
すべてのメモリオーダリングの重要なポイントは、何が起こるかではなく、「何が起こったか」を保証することです。たとえば、いくつかの変数(たとえば、x = 7; y = 11;
)に何かを格納する場合、別のプロセッサは、xに値7
を表示する前に、y
を11として表示できる可能性があります。設定x
と設定y
の間でメモリオーダリング操作を使用することにより、使用しているプロセッサは、y
に何かを格納し続ける前に、x = 7;
がメモリに書き込まれたことを保証します。
ほとんどの場合、値が最終的に更新される限り、書き込みがどの順序で行われるかはそれほど重要ではありません。しかし、たとえば、整数の循環バッファがあり、次のようなことを行う場合。
buffer[index] = 32;
index = (index + 1) % buffersize;
他のスレッドはindex
を使用して新しい値が書き込まれたことを確認しているため、最初に32
を書き込んでから、index
を更新する必要があります。そうしないと、他のスレッドがold
データを取得する可能性があります。
セマフォやミューテックスなどを機能させる場合にも同じことが当てはまります。これが、メモリバリアタイプにリリースと取得という用語が使用される理由です。
現在、cst
は最も厳密な順序付けルールです。これにより、プロセッサがさらに操作を続行する前に、書き込んだデータの読み取りと書き込みの両方がメモリに出力されます。これは、特定の取得または解放の障壁を実行するよりも遅くなります。これにより、プロセッサは、ストアだけまたはロードだけではなく、ストアとロードが完了したことを確認するように強制されます。
それはどのくらいの違いがありますか?これは、システムアーキテクチャが何であるかに大きく依存します。一部のシステムでは、キャッシュを[部分的に]フラッシュする必要があり、あるコアから別のコアに割り込みを送信して、「続行する前にこのキャッシュフラッシュ作業を実行してください」と言います。これには数百サイクルかかる場合があります。他のプロセッサでは、通常のメモリ書き込みよりもわずかな割合で遅くなります。 X86は、これを高速に実行するのに非常に優れています。一部のタイプの組み込みプロセッサ(たとえば、ARMの一部のモデル-わからない?)は、すべてが機能することを保証するために、プロセッサでもう少し作業が必要です。