非原子对象访问的内存序:acquire-release可见性验证
#include <stdatomic.h> #include <stdlib.h> void write(int *j, _Atomic int *i){ *j = rand(); atomic_store_explicit(i, 1, memory_order_release); } void read(int *j, _Atomic int *i){ if(atomic_load_explicit(i, memory_order_acquire)){ int var = *j; atomic_store_explicit(i, 0, memory_order_release); } }
假设int *j和_Atomic int *i指向无关联的同一内存对象,两个线程分别无限循环执行上述write和read函数。请问:当atomic_load_explicit(i, memory_order_acquire)读取到atomic_store_explicit(i, 1, memory_order_release)写入的值时,int var = *j;是否必然读取到同一迭代中*j = rand();写入的值?
提问者的理解:
- 由于程序顺序,
*j = rand();先序于(Sequenced before)atomic_store_explicit(i, 1, memory_order_release);。 - 若
atomic_load_explicit(i, memory_order_acquire)读取到对应写入值,则前者与后者同步于(synchronizes with)。 - 同理,
atomic_load_explicit(i, memory_order_acquire)先序于(Sequenced before)int var = *j;。 - 因此,
*j = rand();线程间先发生于(inter-thread happens before)int var = *j;。
根据C标准5.1.2.4/p20规则,*j = rand();是相对于int var = *j;的可见副作用(a visible side effect)。
核心问题:
通过带有synchronizes-with关系的acquire-release操作,是否真的能为**非原子对象(non-atomic object)**提供可见性保证?*j = rand();是否可能与atomic_store_explicit(i, 1, memory_order_release);重排序?或者atomic_load_explicit(i, memory_order_acquire)是否可能与int var = *j;重排序?
注:x86架构中读写操作不会与同类操作重排序,因此针对x86编译的汇编代码无法提供反例。
回答:
是的,int var = *j;必然会读取到同一迭代中*j = rand();写入的值,acquire-release语义完全可以为非原子对象提供跨线程的可见性保证,且不会发生你担心的重排序。
具体依据C标准的规则:
- release语义约束:
memory_order_release的原子存储操作会阻止编译器和硬件将它之前的任何内存操作(包括非原子的*j = rand();)重排序到它之后。也就是说,*j的写入一定会在i的release存储完成前完成,且所有对*j的修改都会被同步到内存中。 - acquire语义约束:
memory_order_acquire的原子加载操作会阻止编译器和硬件将它之后的任何内存操作(包括非原子的int var = *j;)重排序到它之前。也就是说,var对*j的读取一定会在i的acquire加载完成后执行。 - 线程间先发生关系(inter-thread happens before):结合你梳理的先序关系和
synchronizes-with关系,*j的写入明确线程间先发生于var的读取。根据C标准5.1.2.4/p20,如果一个副作用线程间先发生于一个读取操作,那么这个副作用就是该读取操作的可见副作用,读取必须得到这个副作用的结果。
即使在弱内存模型架构(如ARM、PowerPC)下,编译器和硬件也会严格遵守acquire-release的约束,不会出现你担心的重排序情况。x86因为本身是强内存模型,所以表现更严格,但这并不影响标准层面的保证。
内容的提问来源于stack exchange,提问作者Some Name

