C11原子操作与序列点的关系:松弛内存序下原子操作顺序保障问题
核心结论
序列点规则无法保障anotherAtomicCounter的原子加载内存操作一定发生在FAS操作之后,二者属于完全独立的规则体系。
你给出的代码片段存在语法问题(if判断条件少闭合右括号),修正后如下:
size_t counter = atomic_fetch_sub_explicit(&atomicCounter, 1, memory_order_release); if (counter - 1 == 0 && atomic_load_explicit(&anotherAtomicCounter, 1, memory_order_relaxed) == 0) { //Some code }
规则边界说明
序列点的作用范围
序列点是C标准针对抽象机定义的求值顺序规则,它只能保证逻辑层面的执行先后:&& 左操作数的所有求值完全完成后,才会开始右操作数的求值。也就是说抽象机层面,atomic_load_explicit的函数调用一定发生在FAS执行完成、返回值拿到、且counter - 1 == 0判断为真之后。为什么序列点不能阻止重排序
内存序约束的是不同线程观测到的内存操作顺序,以及编译器、CPU允许的内存访问重排序范围。只要重排序后的执行结果在单线程语境下和抽象机执行结果完全一致(符合as-if规则),就算违反你想要的跨操作顺序,也属于合法优化。
你的代码中两个原子变量互相独立,atomic_load_explicit使用的memory_order_relaxed没有任何同步约束,编译器和CPU完全可以合法地将anotherAtomicCounter的读取操作提前到FAS执行之前,只要把读取结果暂存,等左操作数判断为真之后再拿出来比较即可。这种重排序在单线程下不会改变执行结果,完全不违反序列点规则。
正确实现方案
如果要保证加载操作的内存可见性一定发生在FAS之后,你需要添加内存同步约束:
- 方案1:将
atomic_fetch_sub_explicit的内存序改为memory_order_acq_rel,利用读改写操作的acquire语义阻止后续读操作提前 - 方案2:在左操作数结束后、右操作数开始前插入
atomic_thread_fence(memory_order_acquire),通过内存屏障禁止加载操作重排序到FAS之前
内容的提问来源于stack exchange,提问作者Skyfarmer
相关产品推荐
相关产品推荐

