C ++:std :: atomic <bool>和volatile bool </bool>

时间:2015-04-14 16:59:05

标签: c++ multithreading

我正在阅读Anthony Williams的行动书中的C ++并发。 有这个经典的例子有两个线程,一个产生数据,另一个消耗数据和A.W.写了那段代码非常清楚:

std::vector<int> data;
std::atomic<bool> data_ready(false);

void reader_thread()
{
    while(!data_ready.load())
    {
        std::this_thread::sleep(std::milliseconds(1));
    }
    std::cout << "The answer=" << data[0] << "\n";
}

void writer_thread()
{
    data.push_back(42);
    data_ready = true;
}

而且我真的不明白为什么这个代码与我使用经典的挥发性bool而不是原子的bool不同。 如果有人能够对这个问题敞开心扉,我会感激不尽。 感谢。

3 个答案:

答案 0 :(得分:13)

最大的区别在于此代码是正确的,而bool而不是atomic<bool>的版本具有未定义的行为。

这两行代码会创建竞争条件(正式地说,冲突),因为它们读取和写入同一个变量:

  

阅读器

while (!data_ready)
     

作家

data_ready = true;

根据C ++ 11内存模型,正常变量的竞争条件会导致未定义的行为。

规则见本标准第1.10节,最相关的是:

  

如果

,两个动作可能会并发      
      
  • 它们由不同的线程执行,或
  •   
  • 它们没有排序,至少有一个是由信号处理程序执行的。
  •   
     

程序的执行包含数据竞争,如果它包含两个可能同时发生冲突的动作,其中至少有一个不是原子的,并且除了下面描述的信号处理程序的特殊情况之外,它们都不会发生在另一个之前。任何此类数据争用都会导致未定义的行为。

您可以看到变量是否为atomic<bool>对此规则产生了很大的影响。

答案 1 :(得分:8)

A&#34;经典&#34;正如你所说,bool无法可靠地运作(如果有的话)。这样做的一个原因是编译器可能(并且很可能,至少在启用了优化的情况下)仅从内存加载data_ready,因为没有迹象表明它在reader_thread的上下文中发生了变化

你可以通过使用volatile bool强制每次加载它来解决这个问题(这看起来似乎有效)但是这仍然是关于C ++标准的未定义行为,因为对变量的访问既不是同步的也不是原子的。

您可以使用mutex header中的锁定工具强制执行同步,但这会(在您的示例中)引入不必要的开销(因此std::atomic)。


volatile的问题在于它只保证不会省略指令并保留指令顺序。 volatile 保证内存屏障以强制执行缓存一致性。这意味着处理器A上的writer_thread可以将值写入其缓存(甚至可能是主存储器)而处理器B上没有reader_thread看到它,因为处理器的缓存B与处理器A的缓存不一致。有关更详细的说明,请参阅维基百科上的memory barriercache coherence


更多&#34;复杂&#34;可能会有其他问题。表达式然后x = y(即x += y)需要通过锁同步(或在这个简单的情况下是原子+=),以确保x的值不会在期间发生变化处理

例如

x += y实际上是:

  • 阅读x
  • compute x + y
  • 将结果写回x

如果在计算过程中发生了上下文切换到另一个线程,这可能导致类似这样的事情(2个线程,都执行x += 2;假设x = 0):

Thread A                 Thread B
------------------------ ------------------------
read x (0)
compute x (0) + 2
                 <context switch>
                         read x (0)
                         compute x (0) + 2
                         write x (2)
                 <context switch>
write x (2)

现在x = 2即使有两个+= 2次计算。此效果称为撕裂

答案 2 :(得分:1)

Ben Voigt的回答是完全正确的,仍然有点理论化,并且我已经被一位同事问过了,这对我来说意味着什么&#34;我决定尝试一下运气好一点实际答案。

使用您的样本,&#34;最简单的&#34;可能出现的优化问题如下:

根据标准,优化的执行顺序可能不会改变程序的功能。问题是,仅适用于单线程程序,或多线程程序中的单线程。

所以,对于writer_thread和一个(volatile)bool

data.push_back(42);
data_ready = true;

data_ready = true;
data.push_back(42);

是等价的。

结果就是那个

std::cout << "The answer=" << data[0] << "\n";

可以在不将任何值推入数据的情况下执行。

原子bool确实阻止了这种优化,根据定义它可能不会被重新排序。有原子操作的标志允许语句在操作前移动而不是在后面移动,反之亦然,但那些需要非常高级的编程结构知识及其可能导致的问题...