【发布时间】:2016-08-17 21:55:44
【问题描述】:
我对@987654321@和std::memory_order_release的理解如下:
Acquire 表示在获取围栏之后出现的任何内存访问都不能重新排序到围栏之前。
Release 表示在释放栅栏之前出现的任何内存访问都不能重新排序到栅栏之后。
我不明白为什么特别是对于 C++11 原子库,获取栅栏与加载操作相关联,而释放栅栏与存储操作相关联。
澄清一下,C++11 <atomic> 库允许您以两种方式指定内存栅栏:您可以将栅栏指定为原子操作的额外参数,例如:
x.load(std::memory_order_acquire);
或者您可以使用std::memory_order_relaxed 并单独指定围栏,例如:
x.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire);
我不明白的是,鉴于上述acquire和release的定义,为什么C++11专门将acquire与load联系起来,与商店一起发布?是的,我已经看到了许多示例,这些示例展示了如何使用获取/加载和释放/存储来在线程之间同步,但总的来说,获取围栏(防止语句后内存重新排序)和释放的想法似乎是栅栏(在语句之前防止内存重新排序)与加载和存储的想法是正交的。
那么,例如,为什么编译器不让我说:
x.store(10, std::memory_order_acquire);
我意识到我可以通过使用memory_order_relaxed,然后使用单独的atomic_thread_fence(memory_order_acquire) 语句来完成上述操作,但是,为什么我不能直接使用带有memory_order_acquire 的store?
如果我想确保某个存储(例如 x = 10)发生在其他可能影响其他线程的语句执行之前,可能会出现这种情况。
【问题讨论】:
-
在典型的无锁算法中,你读取一个 atomic 以查看共享资源是否已准备好使用(准备好被获取),然后编写一个 atomic 以指示共享资源已准备好被使用(释放资源)。您不希望在检查原子保护之前移动共享资源的读取;并且您不希望待共享资源的初始化在写入原子后移动,表示释放。
-
在示例中,只有
atomic_thread_fence(std::memory_order_acquire)是真正的栅栏。请参阅标准中的 1.10:5 多线程执行和数据竞争 [intro.multithread],其中说(引用草案 n3797)“没有关联内存位置的同步操作是栅栏并且可以是获取栅栏、释放栅栏,或者同时是获取和释放栅栏。” 相比之下,x.load(std::memory_order_acquire)是一个原子操作,它执行获取 对x的操作,如果该值与将 release 存储到 x 中匹配,则它将是一个 同步操作。 -
在简介中,标准(n3797 草案)并未将获取操作限制为加载操作,并将释放操作限制为存储操作。那是不幸的。您必须转到子句 29.3:1 顺序和一致性 [atomics.order] 才能找到 “memory_order_acquire、memory_order_acq_rel 和 memory_order_seq_cst:加载操作对受影响的内存位置执行获取操作” and “memory_order_release, memory_order_acq_rel, and memory_order_seq_cst:存储操作对受影响的内存位置执行释放操作”
-
@amdn 但即使是“真正的栅栏”也根本不需要产生 CPU 栅栏;它与先前或随后的原子操作交互以产生一些效果。只有非常幼稚的编译器才会将给定的 CPU 指令与“真正的栅栏”的每个源代码出现相关联。
-
“与加载和存储的概念正交” 在原子语义下,读取甚至不是修改顺序中的有序事件。您需要写信才能进入该订单;即使你总是写完全相同的值,完全相同值的写入也是有序的。然后您在修改顺序中谈到该写入事件之后。 (从物理上讲,这意味着缓存已经占用了缓存行。)但是释放读取将是模棱两可的,因为相同写入事件的其他读取没有排序。您会更改语义以在修改顺序中包含读取吗?
标签: c++ memory-barriers lock-free stdatomic memory-model