强内存序原子读写拆分及独立内存屏障的有效性疑问
能否将强内存序的原子加载/存储拆分为松弛操作+独立内存屏障?
基础场景与拆分尝试
先看一个标准的acquire-release语义同步示例,这能保证线程2的断言一定会成功:
// thread 1 // thread 2 data = 100; flag.store(true, std::memory_order_release); while(!flag.load(std::memory_order_acquire)); assert(data == 100);
我原本认为,把原子操作拆成松弛操作+对应内存屏障的写法,和上面的代码完全等价:
// thread 1 // thread 2 data = 100; std::atomic_thread_fence(std::memory_order_release); flag.store(true, std::memory_order_relaxed); while(!flag.load(std::memory_order_relaxed)) std::atomic_thread_fence(std::memory_order_acquire); assert(data == 100);
但Herb Sutter的一场演讲示例让我困惑:他说独立的acquire/release屏障毫无意义,某些场景下必须用全屏障才行。
Herb Sutter的示例与疑问
他的示例大致如下(假设global的读写不会出现撕裂):
// thread 1 // thread 2 widget *temp = new widget(); // 此处标注为需要全屏障,而非release屏障 global = temp; temp2 = global; // 此处标注为需要全屏障,而非acquire屏障 temp2->do_something(); temp2 = global; // 同样需要全屏障 temp2->do_something_else();
他的结论是:这里的屏障必须用全屏障,不能用单独的release/acquire屏障,因为这些屏障没有关联到特定的存储或加载操作。那这个结论是否正确?为什么下面的写法无法正常工作?
// thread 1 // thread 2 widget *temp = new widget(); std::atomic_thread_fence(std::memory_order_release); // a global = temp; temp2 = global; std::atomic_thread_fence(std::memory_order_acquire); // b temp2->do_something(); temp2 = global; std::atomic_thread_fence(std::memory_order_acquire); temp2->do_something_else();
解答:场景不同,结论不同
你的拆分写法在基础场景中完全有效
首先明确:在你的第一个基础示例中,拆分后的代码和原acquire-release语义完全等价。C++标准明确保证:
- 线程1中,
std::atomic_thread_fence(std::memory_order_release)加上后续对原子变量flag的松弛store,效果等同于flag.store(true, std::memory_order_release); - 线程2中,对
flag的松弛load成功后紧跟std::atomic_thread_fence(std::memory_order_acquire),效果等同于flag.load(std::memory_order_acquire)。
这种写法能正常建立线程间的同步关系,断言不会失败。
Herb Sutter的结论针对的是“无原子同步点”的场景
他的示例核心问题在于:global是非原子变量,且没有其他原子操作作为同步锚点。
acquire/release屏障的作用依赖于“原子操作配对”:
- release屏障的约束是:阻止屏障之前的内存操作被重排到后续的原子release操作之后;
- acquire屏障的约束是:阻止屏障之后的内存操作被重排到前置的原子acquire操作之前。
在他的示例里,屏障没有绑定到任何原子操作,相当于没有锚点——release屏障无法保证new widget()的写被同步到线程2,acquire屏障也无法保证线程2的后续操作能看到线程1的写入。此时,只有全屏障(双向阻止所有重排)才能勉强约束内存顺序,但这种场景本身就违反了C++内存模型:非原子变量的跨线程读写属于未定义行为,即使加了全屏障也不是规范的写法。
核心区别:是否有原子同步点
- 你的基础场景中,
flag是原子变量,屏障和原子操作直接配对,能建立合法的跨线程同步关系; - Herb Sutter的示例中,没有原子同步点,独立的acquire/release屏障无法发挥同步作用,只能用全屏障(但这是不规范的场景)。
内容的提问来源于stack exchange,提问作者Sourav Kannantha B
相关产品推荐
相关产品推荐

