いくつかのスレッドがデータを書き込んでいるメモリ領域があると仮定しましょう。その後、別の場所に注意を向け、任意の他のスレッドがデータを読み取れるようにします。ただし、ある時点で、そのメモリ領域を再利用する必要があり、再度書き込みます。
ライター スレッドはブール フラグ ( valid
) を提供します。これは、メモリがまだ有効であることを示します (つまり、まだ再利用されていません)。ある時点で、彼はこのフラグを false に設定し、二度と true に設定することはありません (1 回反転するだけです)。
シーケンシャルな一貫性により、次の 2 つのコード スニペットをライターとリーダーにそれぞれ使用するのが正しいはずです。
...
valid = false;
<write to shared memory>
...
と
...
<read from shared memory>
if (valid) {
<be happy and work with data read>
} else {
<be sad and do something else>
}
...
シーケンシャルな一貫性を確保するために何かをする必要があることは明らかです。つまり、必要な取得と解放のメモリ バリアを挿入します。データをセグメントに書き込む前に、書き込みスレッドでフラグを false に設定します。そして、チェックする前にリーダー スレッドでデータをメモリから読み取る必要がありますvalid
。後者は、 valid が単調であることがわかっているためです。つまり、読み取り後も有効である場合、読み取り中は有効でした。
メモリアクセスとアクセスの間に完全なフェンスを挿入すると、valid
うまくいきます。しかし、valid
原子を作るだけで十分でしょうか?
std::atomic<bool> valid = true;
それで
...
valid.store(false); // RELEASE
<write to shared memory>
...
と
...
<read from shared memory>
if (valid.load()) { // ACQUIRE
<be happy and work with data read>
} else {
<be sad and do something else>
}
...
このシナリオでは、アトミック ストアと読み取りを使用した暗黙の解放操作と取得操作がうまく機能しないようです。ライターの RELEASE は、メモリ アクセスが上に移動することを妨げません(上記のコードだけが下に移動することはできません)。同様に、リーダーの ACQUIRE は、メモリ アクセスが下に移動することを妨げません(下のコードだけが上に移動しない場合があります)。
これが正しい場合、このシナリオを機能させるには、ライター スレッドで ACQUIRE (ロード) が必要であり、リーダー スレッドで RELEASE (ストア) が必要です。または、通常のブール値フラグを使用して、共有ミューテックスを使用してスレッド内の書き込みアクセスと読み取りアクセスを保護することもできます (それに対してのみ!)。そうすることで、両方のスレッドに ACQUIRE と RELEASE の両方を効果的に持つことがvalid
でき、メモリ アクセスからアクセスを分離できます。
したがって、これは と で保護されatomic<bool>
た通常の との間の非常に深刻な違いになります。これは正しいですか?bool
mutex
編集:実際には、アトミックのロードとストアによって暗示されるものに違いがあるようです。std::atomic
C++11のは、それぞれロードとストアmemory_order_seq_cst
ではなく両方 (!) を使用します。memory_order_acquire
memory_order_release
対照的に、tbb::atomic
ではなくmemory_semantics::acquire
andを使用します。memory_semantics::release
memory_semantics::full_fence
したがって、私の理解が正しければ、コードは標準の C++11 アトミックで正しくなりますが、tbb アトミックでは、明示的なmemory_semantics::full_fence
テンプレート パラメーターをロードとストアの両方に追加する必要があります。