原子<bool>与布尔值受互斥锁保护

atomic<bool> vs bool protected by mutex

本文关键字:保护 lt bool gt 原子 布尔值      更新时间:2023-10-16

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

写线程提供一个布尔标志(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>mutex保护的普通bool之间的一个非常严重的差异,这是正确的吗?

Edit:实际上,原子上的load和store所隐含的的含义似乎是不同的。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模板参数来加载和存储。

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

设计缺陷在于错误的假设,即只要读取器在完成读取后检查数据有效性,读写同一内存区域就不是问题。

c++标准称之为数据竞争,它会导致未定义的行为。

正确的解决方案是使用std::shared_mutex来管理对单个写入器和多个读取器的访问