假设我们有一个内存区域,其中一些线程正在写入数据.然后它将注意力转移到其他地方并允许任意其他线程读取数据.但是,在某个时间点,它希望重用该内存区域并再次写入.
写入线程提供布尔标志(有效),表示存储器仍然有效读取(即他还没有重用它).在某些时候,他会将此标志设置为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.我们希望在检查有效之前从读取器线程中的内存中读取数据.后者因为我们知道有效是单调的,即如果它在阅读后仍然有效,则在阅读时有效.
在内存访问和有效访问之间插入一个完整的栅栏就可以了.但是,我想知道,制作有效的原子是否足够?
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,将有效访问与内存访问分开.
所以这将是原子< bool>之间非常严重的差异.和一个受互斥锁保护的常规bool,这是正确的吗?
编辑:在原子上加载和存储所隐含的内容实际上似乎存在差异. C 11的std :: atomic使用memory_order_seq_cst作为(!),而不是memory_order_acquire和memory_order_release分别用于加载和存储.
相反,tbb :: atomic使用memory_semantics :: acquire和memory_semantics :: release而不是memory_semantics :: full_fence.
因此,如果我的理解是正确的,那么代码在标准C 11原子上是正确的,但是对于tbb原子,需要将显式的memory_semantics :: full_fence模板参数添加到加载和存储.
最佳答案 编写器将有效标志切换为false并开始写入数据,而读者可能仍在从中读取数据.
设计缺陷是错误的假设,只要读者在完成读取后检查数据有效性,读取和写入相同的存储区域就不是问题.
C标准将其称为数据竞争,并导致未定义的行为.
正确的解决方案是使用std :: shared_mutex来管理对单个writer和多个reader的访问.