原子<bool> vs bool受互斥

时间:2016-07-01 13:26:28

标签: c++ multithreading mutex atomic memory-fences

假设我们有一个内存区域,其中一些线程正在写入数据。然后它将注意力转移到其他地方并允许任意其他线程读取数据。但是,在某个时间点,它希望重用该内存区域并再次写入它。

写入程序线程提供一个布尔标志(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作为一个原子就足够了吗?

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访问与内存访问分开。

因此atomic<bool>与受bool保护的常规mutex之间存在非常严重的差异,这是正确的吗?

编辑:对于原子上的加载和存储,隐含似乎存在差异。 C ++ 11的std::atomic分别使用memory_order_seq_cst(!)而不是memory_order_acquirememory_order_release来加载和存储。

相反,tbb::atomic使用memory_semantics::acquirememory_semantics::release而不是memory_semantics::full_fence

因此,如果我的理解是正确的,那么使用标准的C ++ 11原子代码是正确的,但是对于tbb原子,需要将显式memory_semantics::full_fence模板参数添加到加载和存储。

1 个答案:

答案 0 :(得分:1)

编写器将valid标志切换为false并开始写入数据,而读者可能仍在从中读取数据。

设计缺陷是错误的假设,只要读者在读完数据后检查数据有效性,读取和写入同一存储区就不是问题。

C ++标准将其称为数据竞争,并导致未定义的行为。

正确的解决方案是使用std::shared_mutex来管理对单个作者和多个读者的访问。