为何C++并发代码中assert可能触发?seq_cst如何优化?
为什么使用acquire/release内存序时assert可能触发,而seq_cst能避免?
你无法理解为何代码中使用std::memory_order_acquire读取、std::memory_order_release写入时,assert(z.load()!=0)仍可能触发,而原书指出需用std::memory_order_seq_cst才能避免,下面详细解释原因及seq_cst的作用:
代码示例
#include <atomic> #include <thread> #include <assert.h> std::atomic<bool> x,y; std::atomic<int> z; 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() { x=false; y=false; z=0; 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可能触发(类似松弛序场景),因为x和y的加载操作都可能读取到false。x和y由不同线程写入,因此每个场景中释放到获取的顺序对其他线程的操作无影响。
原因分析
1. acquire/release内存序的局限性
std::memory_order_release和std::memory_order_acquire的同步关系是一对一绑定的:
write_x中x.store(release)仅与read_x_then_y中x.load(acquire)建立同步,保证read_x_then_y在看到x为true后,能看到write_x之前的所有操作,但对write_y的操作没有任何约束。- 同理,
write_y中y.store(release)仅与read_y_then_x中y.load(acquire)同步,不约束write_x的操作。
这意味着:
read_x_then_y在循环等到x为true后,去读y时,完全可能看到y还是false——因为write_y的release操作没有和这个线程的y.load(acquire)建立同步,CPU或编译器可以重排write_y的操作,或者缓存没同步到这个线程。- 同样,
read_y_then_x等到y为true后,读x也可能看到false。
最终两个读线程都不会执行++z,z保持0,触发assert。
2. std::memory_order_seq_cst的优化作用
std::memory_order_seq_cst是最严格的内存序,它会给所有原子操作建立一个全局一致的总顺序——所有线程观察到的原子操作执行顺序完全相同。
在这个例子中,全局总顺序必然是write_x先于write_y,或者write_y先于write_x:
- 如果
write_x先执行,那么当read_y_then_x读到y为true时,write_x肯定已经完成了(因为全局顺序里它在write_y之前),所以x必然是true,read_y_then_x会执行++z。 - 如果
write_y先执行,那么read_x_then_y读到x为true时,y也必然是true,会执行++z。
无论哪种情况,z至少为1,assert不会触发。
内容的提问来源于stack exchange,提问作者JenyaKh
相关产品推荐
相关产品推荐

