この構造体を見てみましょう:
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
A
writing
フラグを立てますB
writing
フラグをロックし始めますA
valid
フラグ ( ) を読み取り、ブロックfalse
に入るif
A
ペイロードを書き込みますA
true
有効なフラグに書き込みます。明らかに、もう一度A
読み取ると、valid
true
A
writing
フラグをクリアしますB
writing
フラグを立てますB
有効フラグ (false
) の古い値を読み取り、if
ブロックに入るB
ペイロードを書き込みますB
旗に書いtrue
てますvalid
B
writing
フラグをクリアします
出来ないことを願っていますが、実際に「なぜ出来ないのか」という質問に答えようとすると、答えがわかりません。これが私の考えです。
標準からの引用(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
発生するすべての副作用を確認する必要があるためです。A
atomic_flag::clear()
std::memory_order_release
私は正しいですか?
説明。私の推論全体 (間違っているか正しいか) は 29.3.12 に依存しています。ここまででわかったのは、 を無視すれば、であってもatomic_flag
から古いデータを読み取ることができるということです。すべてのスレッドに「常にすぐに見える」という意味ではないようです。求めることができる最大の保証は、読み取る値の一貫した順序ですが、新しいデータを取得する前に古いデータを読み取ることはできます。幸いなことに、すべての操作にはこの重要な機能があります。つまり、常に新しいデータを読み取ります。したがって、( だけでなく) フラグを取得/解放した場合にのみ、期待される動作が得られます。私の主張がわかりましたか (正しいかどうか)?valid
atomic
atomic
atomic_flag::test_and_set()
exchange
writing
valid
編集:私の最初の質問には、質問の核心と比較するとあまりにも多くの注目を集めた次の数行が含まれていました。すでに出された回答との一貫性のためにそれらを残していますが、今質問を読んでいる場合は無視してください.
プレーンでvalid
atomic<bool>
はなくプレーンでbool
atomic<bool>
あることには意味がありますか?さらに、それが である必要がある場合、問題を引き起こさない「最小」メモリ順序制約は何ですか?