依赖读是否需要load-acquire?示例C++代码的并发安全性分析
你的代码存在数据竞争,属于未定义行为,必须调整原子加载的内存顺序才能保证安全。下面结合C++内存模型和ARM64架构详细解释:
核心问题:同步关系缺失
C++标准中,数据竞争的定义是:两个不同线程对同一内存位置执行非原子操作,且至少一个是写入操作,同时没有有效的同步机制约束它们的执行顺序。
在你的代码里:
- 主线程执行
new C{rand()}时,会先分配内存,然后写入C::i(非原子操作),再通过std::memory_order_release把指针存入c_ptr。 - 子线程用
std::memory_order_relaxed加载c_ptr,之后通过指针读取C::i(非原子操作)。
这里的关键是:release-store和relaxed-load之间不构成「同步-于(synchronizes-with)」关系。release语义只能保证,所有在store之前的写入操作,对后续执行acquire-load该原子变量的线程可见;但relaxed-load不具备acquire的同步能力,无法确保主线程初始化C::i的写入对子线程可见。
ARM64架构下的具体风险
ARM64属于弱内存模型,允许store-load重排序:即使主线程先初始化C::i再store指针,硬件或编译器可能让指针的store操作先对子线程可见,而C::i的初始化写入还没完成缓存同步。
你提到的「依赖读」,在ARM64中确实会阻止load-load重排序(也就是不会先读C::i再加载c_ptr),但这只是解决了操作顺序的问题,并没有解决可见性问题。子线程可能拿到了有效的指针,但读取C::i时,主线程的初始化值还没同步到子线程的缓存中,导致读到未初始化的垃圾值,这完全符合数据竞争的定义。
正确的修复方式
将子线程中c_ptr的加载内存顺序改为std::memory_order_acquire:
auto read_ptr = c_ptr.load(std::memory_order_acquire);
此时,主线程的release-store和子线程的acquire-load构成同步关系,保证主线程在store之前所有的写入操作(包括C::i的初始化),对子线程都是可见的,彻底消除数据竞争。
内容的提问来源于stack exchange,提问作者nmr

