![](/img/trans.png)
[英]How C++ Standard prevents deadlock in spinlock mutex with memory_order_acquire and memory_order_release?
[英]C++ memory_order_acquire/release questions
最近了解了 c++ 六個 memory 訂單,對memory_order_acquire
和memory_order_release
感到很困惑,下面是來自 cpp 的示例:
#include <thread>
#include <atomic>
#include <cassert>
std::atomic<bool> x = {false};
std::atomic<bool> y = {false};
std::atomic<int> z = {0};
void write_x() { x.store(true, std::memory_order_seq_cst); }
void write_y() { y.store(true, std::memory_order_seq_cst); }
void read_x_then_y() {
while (!x.load(std::memory_order_seq_cst));
if (y.load(std::memory_order_seq_cst))
++z;
}
void read_y_then_x() {
while (!y.load(std::memory_order_seq_cst));
if (x.load(std::memory_order_seq_cst))
++z;
}
int main() {
std::thread a(write_x);
std::thread b(write_y);
std::thread c(read_x_then_y);
std::thread d(read_y_then_x);
a.join(); b.join(); c.join(); d.join();
assert(z.load() != 0); // will never happen
}
在 cpp 參考頁面中,它說:
此示例演示了需要順序排序的情況。
任何其他排序都可能觸發斷言,因為線程 c 和 d 可能以相反的順序觀察原子 x 和 y 的變化。
所以我的問題是為什么不能在這里使用memory_order_acquire和memory_order_release ? memory_order_acquire 和 memory_order_release 提供什么語義?
一些參考資料: https://en.cppreference.com/w/cpp/atomic/memory_order https://gcc.gnu.org/wiki/Atomic/GCCMM/AtomicSync
順序一致性提供了所有順序一致操作的單一總順序。 因此,如果您在線程 A 中有一個順序一致的存儲,並且在線程 B 中有一個順序一致的加載,並且存儲在加載之前排序(按所述單個總順序),那么 B 會觀察 A 存儲的值。所以基本上是順序一致性保證存儲對其他線程“立即可見”。 發行商店不提供此保證。
正如 Peter Cordes 正確指出的那樣,“立即可見”一詞相當不精確。 “可見性”源於這樣一個事實,即所有 seq-cst 操作都是完全有序的,並且所有線程都遵守該順序。 由於存儲和加載是完全排序的,因此在執行后續加載(以單個總順序)之前,存儲的值變得可見。
不同線程中的獲取/釋放操作之間不存在這樣的總順序,因此不存在可見性保證。 只有在獲取操作觀察到釋放操作的值時,操作才會被排序,但不能保證釋放操作的值何時對執行獲取操作的線程可見。
讓我們考慮一下如果我們在這個例子中使用獲取/釋放會發生什么:
void write_x() { x.store(true, std::memory_order_release); }
void write_y() { y.store(true, std::memory_order_release); }
void read_x_then_y() {
while (!x.load(std::memory_order_acquire));
if (y.load(std::memory_order_acquire))
++z;
}
void read_y_then_x() {
while (!y.load(std::memory_order_acquire));
if (x.load(std::memory_order_acquire))
++z;
}
int main() {
std::thread a(write_x);
std::thread b(write_y);
std::thread c(read_x_then_y);
std::thread d(read_y_then_x);
a.join(); b.join(); c.join(); d.join();
assert(z.load() != 0); // can actually happen!!
}
由於我們無法保證可見性,因此線程c
可能會觀察到x == true
和y == false
,而同時線程d
可能會觀察到y == true
和x == false
。 所以兩個線程都不會增加z
並且斷言會觸發。
For more details about the C++ memory model I can recommend this paper which I have co-authored: Memory Models for C/C++ Programmers
在將信息從一個線程傳遞到另一個線程時,您可以使用 aquire/release - 這是最常見的情況。 不需要對此有順序要求。
在這個例子中,有一堆線程。 兩個線程進行寫操作,而第三個線程粗略地測試x
是否在y
之前准備好,第四個線程測試y
是否在x
之前准備好。 從理論上講,一個線程可能會觀察到x
在y
之前被修改,而另一個線程會看到y
在x
之前被修改。 不完全確定它的可能性有多大。 這是一個不常見的用例。
編輯:您可以可視化該示例:假設每個線程都在不同的 PC 上運行,並且它們通過網絡進行通信。 每對 PC 之間都有不同的 ping。 這里很容易舉一個例子,其中不清楚哪個事件首先發生在x
或y
上,因為每台 PC 會看到事件以不同的順序發生。
我不確定這種效果可能會發生在哪些架構上,但有一些復雜的架構,其中兩個不同的處理器結合在一起。 當然,處理器之間的通信比每個處理器的內核之間的通信要慢。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.