std::memory_order_release 必须与同一原子变量上的 load(..., std::memory_order_acquire) 配对才有效;它仅禁止自身前的读写重排到其后,不保证可见性,也不触发缓存同步,唯有 acquire-load 读到该 release 写入的值时,才能确保看到之前所有写入。
单用不能解决可见性——它必须和在同一个原子变量上配对,才能让非原子写入对另一线程“可观察”。
为什么只在写线程用
还不够?
释放操作本身不“推送”数据,也不强制其他线程立即看到该原子变量的新值。它只做一件事:禁止编译器和 CPU 把它前面的读写操作重排到它后面。
如果读线程没用
去读这个变量,那写线程的
就像发了个没人监听的信号——没有同步关系可言
即使读线程用了
,但目标不是同一个
对象(比如写
,却读
),同步链直接断裂
不保证 cache coherency 协议立刻把数据广播出去,它只保证:一旦读线程通过
看到了这个新值,那它就一定能观察到
之前所有写入
必须和哪个操作配对才有效?
必须和同一原子变量上的
配对。这不是推荐,是 C++ 内存模型的硬性要求:只有这种组合才能建立
synchronizes-with
关系。
错误示例:
—— 不能用
去同步
的读取,除非你也在
上做 acquire-load(但语义错乱)
正确模式:一个变量既当“信标”又承载同步语义,例如
,然后写线程先写
,再
,读线程循环
直到为 true,之后访问
就安全
顺序不可逆:必须写线程先
,读线程后
;若读线程抢在写线程 store 前完成 load,结果未定义
x86 和 ARM 上
的实际表现差异
语义一致,实现不同。忽略这点是跨平台偶发 bug 的主要来源。
C知道
CSDN推出的一款AI技术问答工具
下载
立即学习
“
C++免费学习笔记(深入)
”;
x86:通常不生成额外 CPU 指令,仅插入编译器 barrier(如
后加
),所以“看起来总能跑通”
ARM/AArch64:必须生成带语义的指令,如
(store-release),否则无法满足内存模型约束
常见踩坑:在 x86 上测试时漏写
或误用
,一切正常;一上 ARM 就出现
读到旧值或未初始化值——问题不在逻辑,而在缺少 release 所需的硬件级语义指令
容易被忽略的关键点:非原子变量的写入必须严格位于
之前
这不是“大概早一点”,而是编译器和 CPU 都必须保证的顺序约束。任何穿插在中间的 relaxed 操作、函数调用、甚至空语句,都可能破坏依赖链。
安全写法:
危险写法:
—— 中间 relaxed 操作可能让编译器/CPU 重新调度
到
后面
更隐蔽的陷阱:if 分支、循环、日志打印等看似无害的代码,只要出现在
赋值和
之间,且没显式同步,就可能引入重排风险
std::memory_order_releasestd::memory_order_acquirestore(..., std::memory_order_release)load(..., std::memory_order_acquire)releaseacquirestd::atomicflag.store(true, release)data.load(acquire)releaseacquirereleasestd::memory_order_releaseload(..., std::memory_order_acquire)std::atomic ready{false}; std::atomic data{0}; ready.store(true, release)datadatastd::atomic ready{false}; int data = 0; data = 42;ready.store(true, std::memory_order_release);ready.load(std::memory_order_acquire)datastore(release)load(acquire)std::memory_order_releasemovcompiler barrierstlrreleaserelaxeddatastore(..., release)data = 42; ready.store(true, std::memory_order_release);data = 42; some_relaxed_atomic_op.fetch_add(1, std::memory_order_relaxed); ready.store(true, std::memory_order_release);data = 42storedatarelease