编译器重排可能导致多线程下 data 未更新就被读取;需用 memory_order_release(写端)和 memory_order_acquire(读端)配对实现释放-获取同步,仅 atomic 不足,弱序架构及编译器优化均使其不可靠。

编译器重排会让看似顺序的代码“乱套”
编译器在 -O2 或 -O3 下,只要不破坏单线程语义,就可能把两条无关的写操作调换顺序。比如你写:
data = 42; ready.store(true, std::memory_order_relaxed);
编译器完全可能生成等价于:
ready.store(true, std::memory_order_relaxed); data = 42;
这对单线程没影响,但多线程下,另一个线程可能看到 ready == true 却读到未更新的 data(仍是 0 或垃圾值)。这不是 bug,是合法优化 —— 除非你告诉编译器“这里不能重排”。
std::memory_order_release 和 std::memory_order_acquire 是关键防线
它们不是“让操作变慢”,而是向编译器和 CPU 发出明确指令:哪些内存操作必须被约束在栅栏前后。
立即学习“C++免费学习笔记(深入)”;
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
-
memory_order_release用在写端:保证它前面所有读写(包括非原子变量)不会被重排到它之后 -
memory_order_acquire用在读端:保证它后面所有读写不会被重排到它之前 - 二者配对使用,才能构成“释放-获取同步”,让一个线程的写对另一个线程的读真正可见
单独用 relaxed 对 ready,或不用任何内存序修饰 data,都等于没设防。
常见错误:只加 std::atomic 但不设内存序
很多人以为只要把标志变量改成 std::atomic<bool></bool> 就安全了,其实不是。错误示例:
std::atomic<bool> ready{false};
int data = 0;
// 线程1
data = 42;
ready = true; // 默认是 memory_order_seq_cst,但没显式声明,易被忽略
// 线程2
while (!ready) { } // 默认也是 seq_cst,但 data 非原子,无同步保障
printf("%d\n", data); // 可能输出 0
问题在于:ready = true 虽然是原子写,但若没搭配 memory_order_release,编译器仍可能把 data = 42 拖到它后面;而线程2即使读到 true,也无法保证能看到 data 的新值 —— 因为没有 acquire 栅栏来拉住后续读取。
x86 架构下容易误判“反正硬件强,不用管”
x86 的强内存模型确实让部分重排不出问题,但这只是巧合,不是保证。以下情况依然会出错:
- ARM/AArch64、RISC-V 等弱序架构上,
relaxed几乎必然导致数据不可见 - 即使在 x86,编译器重排仍会发生(CPU 不管,但编译器会),而
acquire/release同时约束编译器和 CPU - LLVM 和 GCC 在高优化等级下对
relaxed原子操作的处理越来越激进,依赖硬件默认行为风险陡增
真正跨平台、可维护的代码,不能靠“x86 碰巧跑通”来验证正确性。

















