[英]How does mixing relaxed and acquire/release accesses on the same atomic variable affect synchronises-with?
我對 C++ memory model 中同步關系的定義有疑問,當放松訪問和獲取/釋放訪問混合在同一個原子變量上時。 考慮以下由全局初始化程序和三個線程組成的示例:
int x = 0;
std::atomic<int> atm(0);
[thread T1]
x = 42;
atm.store(1, std::memory_order_release);
[thread T2]
if (atm.load(std::memory_order_relaxed) == 1)
atm.store(2, std::memory_order_relaxed);
[thread T3]
int value = atm.load(std::memory_order_acquire);
assert(value != 1 || x == 42); // Hopefully this is guaranteed to hold.
assert(value != 2 || x == 42); // Does this assert hold necessarily??
我的問題是T3
中的第二個斷言是否會在 C++ memory model 下失敗。請注意, 這個 SO 問題的答案表明,如果T2
使用加載/獲取和存儲/釋放,則斷言不會失敗; 如果我弄錯了,請糾正我。 然而,如上所述,答案似乎取決於在這種情況下如何准確定義同步關系。 我被 cppreference 上的文字弄糊塗了,我想出了以下兩種可能的解讀。
第二個斷言失敗。 T1
中存儲到atm
在概念上可以理解為存儲1_release
,其中_release
是指定值存儲方式的注解; 同樣, T2
中的存儲可以理解為存儲2_relaxed
。 因此,如果T3
中的加載返回2
,線程實際上讀取2_relaxed
; 因此, T3
中的加載與T1
中的存儲不同步,並且不能保證T3
看到x == 42
。 但是,如果T3
中的加載返回1
,則讀取了1_release
,因此T3
中的加載與T1
中的存儲同步,並且T3
保證看到x == 42
。
第二個斷言成功。 如果T3
中的負載返回2
,則此負載讀取T2
中寬松存儲的副作用; 但是,僅當atm
的修改順序包含具有釋放語義的先前存儲時, T2
的此存儲才會出現在atm
的修改順序中。 因此, T3
中的加載/獲取與T1
的存儲/釋放同步,因為后者在atm
的修改順序中必然先於前者。
乍一看, 這個 SO 問題的答案似乎表明我的閱讀 1 是正確的。 然而,這個答案似乎有微妙的不同:答案中的所有存儲都是釋放的,問題的關鍵是看到加載/獲取和存儲/釋放在一對線程之間建立同步。 相比之下,我的問題是當 memory 訂單是異構時,如何准確定義 synchronises-with。
我實際上希望閱讀 2 是正確的,因為這會使關於並發的推理更容易。 線程T2
不讀取或寫入除atm
以外的任何 memory; 因此, T2
本身沒有同步要求,因此應該能夠使用寬松的 memory 順序。 相比之下, T1
發布x
而T3
使用它——也就是說,這兩個線程相互通信,因此它們應該清楚地使用獲取/釋放語義。 換句話說,如果解釋 1 被證明是正確的,那么僅僅考慮T2
做了什么就不能編寫代碼T2
; 相反, T2
的代碼需要知道它不應該“干擾” T1
和T3
之間的同步。
無論如何,了解在這種情況下標准究竟批准了什么對我來說似乎是絕對重要的。
因為您在 T2 中的單獨加載和存儲上使用了寬松的排序,所以釋放序列被破壞並且可以觸發第二個斷言(盡管不是在 X86 等 TSO 平台上)。
您可以通過在線程 T2 中使用 acq/rel 排序(如您建議的那樣)或修改 T2 以使用原子讀-修改-寫操作 (RMW) 來解決此問題,如下所示:
[Thread T2]
int ret;
do {
int val = 1;
ret = atm.compare_exchange_weak(val, 2, std::memory_order_relaxed);
} while (ret != 0);
atm
的修改順序是 0-1-2 並且 T3 將在 1 或 2 上拾取並且任何斷言都不會失敗。
T2 的另一個有效實現是:
[thread T2]
if (atm.load(std::memory_order_relaxed) == 1)
{
atm.exchange(2, std::memory_order_relaxed);
}
這里的RMW本身是無條件的,必須伴隨着if語句&(relaxed)load來保證atm
的修改順序是0-1或者0-1-2
如果沒有 if 語句,修改順序可能是 0-2,這會導致斷言失敗。 (這是有效的,因為我們知道在程序的整個 rest 中只有一個其他寫入。單獨的if()
/ exchange
當然通常不等同於compare_exchange_strong
。)
在C++標准中,相關引用如下:
[介紹比賽]
以原子 object M 上的釋放操作 A 為首的釋放序列是 M 修改順序中副作用的最大連續子序列,其中第一個操作是 A,隨后的每個操作都是原子讀-修改-寫操作。
[atomics.order]
對原子 object M 執行釋放操作的原子操作 A 與對 M 執行獲取操作的原子操作 B 同步,並從以 A 為首的釋放序列中的任何副作用中獲取其值。
這個問題是關於為什么RMW 在發布序列中工作。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.