memory_order 是线程同步契约,非随意配置:relaxed 不保证顺序,release/acquire 必须配对且同变量,seq_cst 代价高不宜滥用,consume 已基本废弃;关键在锚定非原子访问。

直接说结论:memory_order 不是“选一个就行”的配置项,它是线程间同步的契约——用错一个,assert 可能永远不触发,但程序在特定 CPU 或负载下会静默出错。
为什么 memory_order_relaxed 不能用来同步数据
它只保证那个 store 或 load 本身不被撕开,但编译器和 CPU 完全可以把它前后的非原子读写任意重排。常见错误是:先写 data = 42,再用 flag.store(true, std::memory_order_relaxed) 通知对方,结果对方看到 flag == true 时,data 还是 0。
- 这不是偶发 bug,是标准允许的行为
- ARM/AArch64 上尤其容易暴露,x86 因强内存模型可能“碰巧”通过
- 调试时加
std::this_thread::yield()或日志反而可能掩盖问题(干扰重排时机)
memory_order_release 和 memory_order_acquire 必须配对使用
单独用其中一个毫无意义。只有当一个线程用 release 写,另一个线程用 acquire 读同一个原子变量时,才能建立 “synchronizes-with” 关系,从而让 release 前的所有写操作对 acquire 后的读操作可见。
- 不能跨变量配对:线程 A 对
flag1.store(..., release),线程 B 对flag2.load(..., acquire)—— 无效 - 不能反向配对:A 用
acquire读,B 用release写 —— 不构成同步 - 中间插入非原子操作不影响:只要它们操作的是同一个原子变量,且无其他线程干扰该变量的修改
memory_order_seq_cst 是默认值,但不是“安全兜底”
它确实最简单:所有 seq_cst 操作形成单一全局修改顺序,你不用动脑想配对。但它代价高——在 x86 上通常插入 mfence,在 ARM 上需显式 dmb ish,还会抑制编译器优化。
- 别因为怕出错就全用
seq_cst:高频计数器、无依赖状态位,relaxed更合适 - 混合使用风险大:一个变量上混用
relaxed和seq_cst,语义边界模糊,容易漏掉关键同步点 -
seq_cstload +seq_cststore 的性能,可能比acquire+release差 2–3 倍(实测于 ARM64 环境)
memory_order_consume 基本等于废掉
C++20 明确不鼓励使用,主流编译器(GCC、Clang)实际都把它当作 acquire 处理。它的本意是只约束“数据依赖链”上的重排(比如指针解引用),但太难正确推理,也难硬件支持。
- 你写
consume,编译器大概率升格为acquire,但你按consume逻辑写的代码,可能在某天被优化掉依赖判断而崩 - 除非你在写 Linux kernel 级别代码并明确针对特定架构,否则直接用
acquire - 标准里没删它,只是“deprecated”,但没人真靠它构建可靠同步
真正难的从来不是记住六种枚举值,而是判断哪段非原子内存访问必须被哪个原子操作“锚定”。一旦漏掉一个 release,或把 acquire 放错位置,bug 就藏进多核缓存一致性缝隙里,等上线后某个凌晨三点才浮出来。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











