C++11中用Acquire/Release替换Seq_CST后断言是否会失败?
问题解答:将seq_cst替换为acquire/release后assert是否会触发失败?
先看调整后的代码:
#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_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); }
答案是:这个assert确实有可能触发失败。下面结合内存序的特性来解释:
我们先对应你提到的几个内存序约束来分析:
std::memory_order_seq_cst确实能阻止StoreLoad重排,但换成acquire/release后就没有这个保障了,这也是问题的核心。acquire确实能阻止LoadLoad重排:比如线程c里的x.load(acquire)之后的y.load(acquire)绝对不会被重排到x的load之前,线程d同理,这部分没问题。release确实能阻止StoreStore重排:但这里两个写线程都只有一个store操作,所以这个约束在当前代码里没发挥额外作用。
关键问题在于,acquire/release只建立了单向的可见性保证(写线程的操作对读到该store的读线程可见),但没有像seq_cst那样强制所有原子操作遵循一个全局的总执行顺序。这就可能出现以下场景:
- 线程a的
x.store(release)的结果被线程c看到了,所以c退出自旋,但此时线程b的y.store(release)的结果还没同步到c所在的核心,c读到y仍然是false,不会递增z; - 同时,线程b的
y.store(release)的结果被线程d看到了,d退出自旋,但线程a的x.store(release)的结果还没同步到d所在的核心,d读到x仍然是false,也不会递增z;
最终z的值还是0,触发assert(z.load() != 0)失败。
而如果用seq_cst的话,所有原子操作都有一个全局总序,要么x的store在y的store之前,要么反过来,这样至少有一个读线程能看到两个变量都为true,z会被递增,assert不会失败。
内容的提问来源于stack exchange,提问作者Pengcheng
相关产品推荐
相关产品推荐

