下面是用于跨线程数据同步的获取-释放语义的简单示例。
// thread 1 // thread 2
data = 100;
flag.store(true, std::memory_order_release);
while(!flag.load(std::memory_order_acquire));
assert(data == 100);
据我了解,这准确地表明了获取-释放内存顺序的使用,并且程序将按预期工作。
但如果我使用独立屏障会怎样?
// thread 1 // thread 2
data = 100;
std::atomic_thread_fence(std::memory_order_release);
flag.store(true, std::memory_order_relaxed);
while(!flag.load(std::memory_order_relaxed))
std::atomic_thread_fence(std::memory_order_acquire);
assert(data == 100);
我一直以为这是exactly相当于第一个例子。
但今天我在 CppCon 上观看了 Herb Sutter 的演讲(C++ 及未来 2012 年:Herb Sutter - 原子武器 https://youtu.be/A8eCGOqgvH4?t=4030). At 1:07:10在视频中,他举了一个例子来表明独立围栏并不是最理想的。看完后我很困惑。
例子是这样的:
// thread 1 // thread 2
widget *temp = new widget();
XX mb(); XXXXXXXXXXXXXXXXXXXXX // a
global = temp;
temp2 = global;
XX mb(); XXXXXXXXXXXXXXXXXXXXX // b
temp2->do_something();
temp2 = global;
XX mb(); XXXXXXXXXXXXXXXXXXXXX
temp2->do_something_else();
他说,在 a 和 b 处,您需要一个完整的屏障,而不仅仅是释放和获取,因为它们与任何特定的存储或负载无关。此外,他说,独立的获取和释放障碍没有任何意义。它是否正确? (为了简单起见,假设对 global 的读取和写入是不可分割的,即 global 永远不会包含撕裂值)。
为什么这不起作用?
// thread 1 // thread 2
widget *temp = new widget();
XX release(); XXXXXXXXXXXXXXXX // a
global = temp;
temp2 = global;
XX acquire(); XXXXXXXXXXXXXXXX // b
temp2->do_something();
temp2 = global;
XX acquire(); XXXXXXXXXXXXXXXX
temp2->do_something_else();