我有一個使用std::atomic_thread_fences 的以下程式:
int data1 = 0;
std::atomic<int> data2 = 0;
std::atomic<int> state;
int main() {
state.store(0);
data1 = 0;
data2 = 0;
std::thread t1([&]{
data1 = 1;
state.store(1, std::memory_order_release);
});
std::thread t2([&]{
auto s = state.load(std::memory_order_relaxed);
if (s != 1) return;
std::atomic_thread_fence(std::memory_order_acquire);
data2.store(data1, std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_release);
state.store(2, std::memory_order_relaxed);
});
std::thread t3([&]{
auto d = data2.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire);
if (state.load(std::memory_order_relaxed) == 0) {
std::cout << d;
}
});
t1.join();
t2.join();
t3.join();
}
它由 3 個執行緒和一個state用于同步的全域原子變陣列成。第一個執行緒將一些資料寫入一個全域非原子變數data1并設定state為 1。第二個執行緒讀取state,如果它等于1它修改分配data1給另一個全域非原子變數data2。之后,它存盤2到state. 該執行緒讀取 的內容,data2然后檢查state.
問:第三個執行緒總是列印0嗎?或者第三個執行緒是否有可能在 update todata2之前看到 update to state?如果是這樣,是保證使用 seq_cst 記憶體順序的唯一解決方案嗎?
uj5u.com熱心網友回復:
我認為 t3 可以列印 1。
我認為基本問題是 t2 中的釋放柵欄放錯了位置。它應該在要“升級”以釋放的存盤之前進行排序,以便所有較早的加載和存盤在以后的存盤之前變得可見。在這里,它具有“升級”的效果state.store(2)。但這沒有幫助,因為沒有人試圖使用該條件state.load() == 2來訂購任何東西。所以 t2 中的釋放柵欄與 t3 中的獲取柵欄不同步。因此,您不會在 t3 中的任何加載之前發生任何存盤,因此您根本無法保證它們可能回傳什么值。
柵欄真的應該先走data2.store(data1),然后它應該作業。您可以放心,任何觀察該商店的人隨后都會觀察所有先前的商店。這將包括state.store(1)由于 t1 和 t2 之間的釋放/獲取對而較早訂購的 t1。
因此,如果您將 t2 更改為
auto s = state.load(std::memory_order_relaxed);
if (s != 1) return;
std::atomic_thread_fence(std::memory_order_acquire);
std::atomic_thread_fence(std::memory_order_release); // moved
data2.store(data1, std::memory_order_relaxed);
state.store(2, std::memory_order_relaxed); // irrelevant
然后每當data2.load()t3 回傳 1 時,t2 中的釋放柵欄與 t3 中的獲取柵欄同步(參見 C 20 atomics.fences p2)。t2 存盤data2僅在 t2 加載state回傳 1 時發生,這將確保 t1 中的釋放存盤與 t2 中的獲取柵欄(atomics.fences p4)同步。然后我們有
t1 state.store(1)
synchronizes with
t2 acquire fence
sequenced before
t2 release fence
synchronizes with
t3 acquire fence
sequenced before
t3 state.load()
所以這state.store(1)發生在 之前state.load(),因此state.load()在這種情況下不能回傳 0。這將確保所需的排序而不需要seq_cst.
想象一下原始代碼實際上是如何失敗的,想想像 POWER 之類的東西,其中某些核心組在它們到達 L1 快取并成為全域可見之前,可以從彼此的存盤緩沖區中獲得對窺探存盤的特殊早期訪問權限。然后獲取屏障只需要等到所有先前的加載完成;而釋放屏障不僅應該耗盡它自己的存盤緩沖區,還應該耗盡它可以訪問的所有其他存盤緩沖區。
所以假設 core1 和 core2 是這樣一個特殊的對,但是 core3 距離更遠,只有在將存盤寫入 L1 快取后才能看到它們。我們可以有:
core1 core2 L1 cache core3
===== ===== ======== =====
data1 <- 1
release data1 <- 1
state <- 1
(still in store buffer)
1 <- state
acquire
1 <- data1
data2 <- 1 data2 <- 1
1 <- data2
acquire
0 <- state
release state <- 1
state <- 2 state <- 2
核心 2 中的釋放屏障確實會導致核心 1 的存盤緩沖區耗盡并因此寫入state <- 1L1 快取,但到那時為時已晚。
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/475208.html
下一篇:Java無法編輯執行緒秒表超時
