假设我们有一个内存区域,某个线程正在向其中写入数据。然后它将注意力转移到其他地方并允许任意其他线程读取数据。但是,在某个时间点,它想重用该内存区域并将再次写入。
写线程提供了一个布尔标志(valid
),它表明内存仍然可以读取(即他还没有重用它)。在某个时候,他会将此标志设置为 false 并且永远不会再次将其设置为 true(它只会翻转一次,仅此而已)。
在顺序一致性的情况下,分别为编写者和阅读者使用这两个代码片段应该是正确的:
...
valid = false;
<write to shared memory>
...
和
...
<read from shared memory>
if (valid) {
<be happy and work with data read>
} else {
<be sad and do something else>
}
...
我们显然需要做一些事情来确保顺序一致性,即插入必要的获取和释放内存屏障。在将任何数据写入段之前,我们希望在编写器线程中将该标志设置为 false 。我们希望在检查之前从阅读器线程中的内存中读取数据valid
。后者是因为我们知道valid 是单调的,即如果它读后仍然有效,那么它在读时是有效的。
在内存访问和访问之间插入一个完整的栅栏valid
就可以了。然而,我想知道,如果制造valid
一个原子就足够了吗?
std::atomic<bool> valid = true;
然后
...
valid.store(false); // RELEASE
<write to shared memory>
...
和
...
<read from shared memory>
if (valid.load()) { // ACQUIRE
<be happy and work with data read>
} else {
<be sad and do something else>
}
...
似乎在这种情况下,使用原子存储和读取的隐含释放和获取操作对我不利。编写器中的 RELEASE不会阻止将内存访问向上移动(只是上面的代码可能不会向下移动)。同样,阅读器中的 ACQUIRE不会阻止将内存访问向下移动(只是下面的代码可能不会向上移动)。
如果这是真的,为了使这个场景工作,我还需要在编写线程中进行 ACQUIRE(即加载),并在读取线程中进行 RELEASE(即存储)。或者,我可以只使用一个普通的布尔标志,并使用共享互斥锁保护线程中的写入和读取访问(仅限于它!)。通过这样做,我将有效地在两个线程中同时拥有 ACQUIRE 和 RELEASE,从而将valid
访问与内存访问分开。
因此,这将是与受a 保护atomic<bool>
的常规之间非常严重的区别,这是正确的吗?bool
mutex
编辑:实际上,加载和存储原子所暗示的内容似乎有所不同。std::atomic
C++11的memory_order_seq_cst
用于 (!),而不是memory_order_acquire
分别memory_order_release
用于加载和存储。
相比之下,tbb::atomic
使用memory_semantics::acquire
andmemory_semantics::release
而不是memory_semantics::full_fence
.
因此,如果我的理解是正确的,那么对于标准 C++11 原子,代码将是正确的,但对于 tbb 原子,则需要将显式memory_semantics::full_fence
模板参数添加到加载和存储中。