为何Relaxed内存序在该C++示例中表现与Sequential Consistent一致?
我测试了cppreference和《C++ Concurrency in Action》(Anthony Williams著)清单5.4中的示例,将所有memory_order_seq_cst替换为memory_order_relaxed后,上万次测试里assert都没有触发。但cppreference明确说明“任何其他内存序都可能触发assert,因为线程c和d可能以相反顺序观察到原子变量x和y的变化”,Anthony也提到“该约束不会延伸到使用relaxed内存序的原子操作线程”,这和我的测试结果矛盾。
我的问题是:为什么Relaxed内存序在这个示例中表现和Sequential Consistent内存序完全一致?
测试步骤
# 编译 c++ example.cc # 重复测试100000次 for i in {1..100000}; do ./a.out; done; # 所有测试均成功,无abort情况
测试代码
#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_relaxed); } void write_y() { y.store(true, std::memory_order_relaxed); } void read_x_then_y() { while (!x.load(std::memory_order_relaxed)) ; if (y.load(std::memory_order_relaxed)) { ++z; } } void read_y_then_x() { while (!y.load(std::memory_order_relaxed)) ; if (x.load(std::memory_order_relaxed)) { ++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); // 从未触发 }
问题解析
首先要明确:C++内存模型定义的是允许出现的行为,不是一定会出现的行为。cppreference和书中的表述是说relaxed内存序下assert有可能触发,而不是必然触发。你的测试没触发,只是没碰到那个概率极低的执行路径而已。
具体来说,这种“反向观察”的情况需要满足几个极端条件:
- 线程c看到x被设置为true,但此时y的更新还没同步到它的缓存;
- 线程d看到y被设置为true,但此时x的更新还没同步到它的缓存;
- 两个线程各自完成后续操作后,z的值依然是0,最终触发assert。
现代CPU的缓存同步机制(比如MESI协议)以及编译器的优化策略,会让这种极端情况很难出现。尤其是在x86架构下,CPU本身对内存操作有较强的一致性保证(默认接近seq_cst的顺序一致性,仅允许store-load重排),而你的代码里没有store-load的场景,所以relaxed和seq_cst的表现几乎一致。
另外,测试次数多不代表能覆盖所有可能的执行路径。这种竞态条件的触发依赖于CPU调度、缓存刷新的精确时机,可能需要百万甚至千万次测试才有可能碰到一次,或者在特定的弱一致性架构(比如部分老款ARM)上更容易重现。
总结:relaxed内存序确实允许assert触发的情况存在,只是你的测试环境和次数还没碰到而已,这和内存模型的定义并不矛盾。
内容的提问来源于stack exchange,提问作者absuu

