ロックフリーのコードを書いていて、興味深いパターンを思いつきましたが、緩和されたメモリ順序で期待どおりに動作するかどうかはわかりません。
それを説明する最も簡単な方法は、例を使用することです。
std::atomic<int> a, b, c;
auto a_local = a.load(std::memory_order_relaxed);
auto b_local = b.load(std::memory_order_relaxed);
if (a_local < b_local) {
auto c_local = c.fetch_add(1, std::memory_order_relaxed);
}
すべての操作は を使用することに注意してくださいstd::memory_order_relaxed
。
明らかに、これが実行されるスレッドでは、条件が評価される前にロードを実行する必要がありますa
。b
if
同様に、読み取り-変更-書き込み (RMW) 操作c
は、条件が評価された後に実行する必要があります (その条件に基づいているため)。
私が知りたいのは、このコードは、 の値がc_local
少なくとも および の値と同じくらい最新であるa_local
ことを保証するb_local
かということです。もしそうなら、緩和されたメモリの順序付けを考えると、これはどのように可能ですか? コントロールの依存関係は、何らかの取得フェンスとして機能する RWM 操作と共にありますか? (対応するリリースさえどこにもないことに注意してください。)
上記が当てはまる場合、この例も機能するはずです(オーバーフローがないことを前提としています)-私は正しいですか?
std::atomic<int> a(0), b(0);
// Thread 1
while (true) {
auto a_local = a.fetch_add(1, std::memory_order_relaxed);
if (a_local >= 0) { // Always true at runtime
b.fetch_add(1, std::memory_order_relaxed);
}
}
// Thread 2
auto b_local = b.load(std::memory_order_relaxed);
if (b_local < 777) {
// Note that fetch_add returns the pre-incrementation value
auto a_local = a.fetch_add(1, std::memory_order_relaxed);
assert(b_local <= a_local); // Is this guaranteed?
}
a
スレッド 1 には、インクリメントされる前に常にインクリメントされることが保証されていると思われる制御依存関係がb
あります (ただし、それらは首から首までインクリメントされ続けます)。スレッド 2 には、インクリメントされる前にb
ロードされることが保証されていると思われる別の制御依存関係があります。また、 から返される値は、少なくとも で観測された値と同じくらい新しいものになると思います。したがって、 が保持されるはずです。しかし、これは通常のメモリ順序付けの例とは大きくかけ離れているため、確信が持てません。また、C++11 メモリ モデルについての私の理解は完全ではありません (これらのメモリ順序付け効果について、ある程度の確信を持って推論するのは困難です)。どんな洞察もいただければ幸いです!b_local
a
fetch_add
b_local
assert
更新:bames53 がコメントで有益に指摘したように、十分にスマートなコンパイラがあればif
、適切な状況下で完全に最適化される可能性があります。その場合、緩和されたロードが RMW の後に発生するように並べ替えられ、それらの値fetch_add
戻り値よりも最新であること (assert
私の 2 番目の例では発火する可能性があります)。しかし、 の代わりにif
( atomic_signal_fence
not atomic_thread_fence
) を挿入するとどうなるでしょうか。どのような最適化が行われたとしても、これはコンパイラーが無視することはできませんが、コードが期待どおりに動作することを保証しますか? このような場合、CPU は並べ替えを行うことができますか?
2 番目の例は次のようになります。
std::atomic<int> a(0), b(0);
// Thread 1
while (true) {
auto a_local = a.fetch_add(1, std::memory_order_relaxed);
std::atomic_signal_fence(std::memory_order_acq_rel);
b.fetch_add(1, std::memory_order_relaxed);
}
// Thread 2
auto b_local = b.load(std::memory_order_relaxed);
std::atomic_signal_fence(std::memory_order_acq_rel);
// Note that fetch_add returns the pre-incrementation value
auto a_local = a.fetch_add(1, std::memory_order_relaxed);
assert(b_local <= a_local); // Is this guaranteed?
別の更新: これまでのすべての応答を読み、自分で標準を調べた後、標準のみを使用してコードが正しいことを示すことはできないと思います。では、標準に準拠し、アサートも実行する理論的システムの反例を思い付くことができる人はいますか?