加载内存中的指针时是否需要使用内存获取屏障?
加载内存中的指针时是否需要使用内存获取屏障?
这个问题问得很实际,咱们先从你给出的第一个经典例子入手,理清release-acquire屏障的核心作用,再回头分析你拿不准的场景。
首先看第一个标准同步案例:
int x = 0; int y = 0; void thread1(void) { x = 1; atomic_store_explicit(&y, 1, memory_order_release); } void thread2(void) { int tmp_y = atomic_load_explicit(&y, memory_order_acquire); int tmp_x = x; if (tmp_y) assert(tmp_x); // must be ok }
这里的memory_order_release和memory_order_acquire是配对的同步机制:当thread2通过acquire加载读到y=1时,thread1中所有在release存储y之前的写操作(也就是x=1),对thread2中acquire加载之后的读操作(tmp_x=x)都是完全可见的,所以断言一定成立,这是release-acquire语义的核心价值。
接下来分析你拿不准的场景:
int x = 0; int *px = NULL; void thread1(void) { x = 1; atomic_store_explicit(&px, &x, memory_order_release); } void thread2(void) { int *p = atomic_load_explicit(&px, memory_order_relaxed); // Do we need memory_order_acquire here? if (p) assert(*p); // is it ok? }
答案是必须把这里的memory_order_relaxed换成memory_order_acquire,否则断言不一定成立,具体原因如下:
- thread1里的
memory_order_release确实保证了x=1不会被编译器或CPU重排到atomic_store_explicit(&px, &x)之后,但这只是thread1内部的顺序约束,没法直接作用到thread2。 - 如果thread2用
memory_order_relaxed加载px,这个加载操作和后续的*p(也就是读x)之间没有任何同步绑定:- 编译器可能会把
*p的读操作重排到加载px之前,导致读到x的初始值0; - 即使线程2读到了非NULL的px,CPU的缓存一致性机制也不能保证x的最新值已经同步到thread2的缓存中——没有acquire屏障触发缓存同步的话,thread2可能还会看到x的旧值。
- 编译器可能会把
只有当thread2用memory_order_acquire加载px时,一旦读到了thread1写入的&x,就会和thread1的release存储建立跨线程的同步关系,此时thread1中所有在release存储之前的写操作(x=1),对thread2中acquire加载之后的读操作(*p)都是可见的,断言才能保证稳定成立。
简单来说:你可以把指针px看作是一个“就绪信号”,发信号的thread1用release保证“信号发出前所有准备工作都完成”,接收信号的thread2必须用acquire保证“看到信号后,能获取到信号发出前的所有准备结果”,二者缺一不可。
备注:内容来源于stack exchange,提问作者untitled
相关产品推荐
相关产品推荐

