この構造体を見てみましょう:
struct entry {
atomic<bool> valid;
atomic_flag writing;
char payload[128];
}
2 つの踏面 A と B は、この構造体に次のように同時にアクセスします ( をeのインスタンスにしますentry)。
if (e.valid) {
// do something with e.payload...
} else {
while (e.writing.test_and_set(std::memory_order_acquire));
if (!e.valid) {
// write e.payload one byte at a time
// (the payload written by A may be different from the payload written by B)
e.valid = true;
e.writing.clear(std::memory_order_release);
}
}
このコードは正しく、問題はないと思いますが、なぜ機能するのかを理解したいと思います。
C++ 標準 (29.3.13) の引用:
実装では、妥当な時間内にアトミック ストアをアトミック ロードで認識できるようにする必要があります。
ここで、これを念頭に置いて、スレッド A と B の両方がelseブロックに入ると想像してください。このインターリーブは可能ですか?
- どちらも
A出店Bするelseのでvalid、false Awritingフラグを立てますBwritingフラグをロックし始めますAvalidフラグ ( ) を読み取り、ブロックfalseに入るifAペイロードを書き込みますAtrue有効なフラグに書き込みます。明らかに、もう一度A読み取ると、validtrueAwritingフラグをクリアしますBwritingフラグを立てますB有効フラグ (false) の古い値を読み取り、ifブロックに入るBペイロードを書き込みますB旗に書いtrueてますvalidBwritingフラグをクリアします
出来ないことを願っていますが、実際に「なぜ出来ないのか」という質問に答えようとすると、答えがわかりません。これが私の考えです。
標準からの引用(29.3.12):
アトミックな read-modify-write 操作は、read-modify-write 操作に関連付けられた書き込みの前に書き込まれた (変更順序での) 最後の値を常に読み取る必要があります。
atomic_flag::test_and_set()29.7.5 で述べられているように、アトミックな read-modify-write 操作です。
atomic_flag::test_and_set()は常に「新しい値」を読み取り、std::memory_order_acquireメモリの順序付けで呼び出しているため、フラグの古い値を読み取ることはできません。これは、呼び出し (を使用する)の前にvalid発生するすべての副作用を確認する必要があるためです。Aatomic_flag::clear()std::memory_order_release
私は正しいですか?
説明。私の推論全体 (間違っているか正しいか) は 29.3.12 に依存しています。ここまででわかったのは、 を無視すれば、であってもatomic_flagから古いデータを読み取ることができるということです。すべてのスレッドに「常にすぐに見える」という意味ではないようです。求めることができる最大の保証は、読み取る値の一貫した順序ですが、新しいデータを取得する前に古いデータを読み取ることはできます。幸いなことに、すべての操作にはこの重要な機能があります。つまり、常に新しいデータを読み取ります。したがって、( だけでなく) フラグを取得/解放した場合にのみ、期待される動作が得られます。私の主張がわかりましたか (正しいかどうか)?validatomicatomicatomic_flag::test_and_set()exchangewritingvalid
編集:私の最初の質問には、質問の核心と比較するとあまりにも多くの注目を集めた次の数行が含まれていました。すでに出された回答との一貫性のためにそれらを残していますが、今質問を読んでいる場合は無視してください.
プレーンでvalidatomic<bool>はなくプレーンでboolatomic<bool>あることには意味がありますか?さらに、それが である必要がある場合、問題を引き起こさない「最小」メモリ順序制約は何ですか?