多线程是否需要原子类型?(OS X,clang,c ++ 11)

nev*_*stn 5 c++ multithreading gcc clang c++11

我试图证明不使用std::atomic<>s 是一个非常糟糕的主意,但我无法创建一个再现失败的例子.我有两个线程,其中一个做:

{
    foobar = false;
}
Run Code Online (Sandbox Code Playgroud)

和另外一个:

{
    if (foobar) {
        // ...
    }
}
Run Code Online (Sandbox Code Playgroud)

类型foobar或者是bool或者std::atomic_bool和它的初始化true.我正在使用OS X Yosemite,甚至尝试使用技巧来提示我希望线程在不同内核上运行的CPU亲和性.我在循环等中运行这样的操作,无论如何,执行中没有可观察到的差异.我最终用clang检查生成的程序集,clang -std=c++11 -lstdc++ -O3 -S test.cpp我发现读取的asm差异很小(左边没有原子,右边没有原子):

在此输入图像描述

不,mfence或"戏剧性"的东西.在写作方面,会发生更具"戏剧性"的事情:

在此输入图像描述

如您所见,atomic<>版本xchgb使用了隐式锁.当我使用相对旧版本的gcc(v4.5.2)编译时,我可以看到mfence添加了各种各样的s,这也表明存在严重问题.

我有点理解"X86实现了一个非常强大的内存模型"(参考)并且mfence可能没有必要,但它是否意味着除非我想编写支持ARM的跨平台代码,否则我真的不需要atomic<>除非我关心ns级别的一致性,否则放任何s?

我看过"原子<>武器"从香草萨特,但我与它是多么的困难,以创建一个简单的例子,再现这些问题仍然印象深刻.

Seb*_*edl 5

数据竞争的一个大问题是它们是未定义的行为,不能保证错误的行为.而且,结合线程的一般不可预测性和x64内存模型的强度,这意味着创建可重现的故障变得非常困难.

稍微更可靠的故障模式是优化器执行意外操作时,因为您可以在程序集中观察它们.当然,优化器也是众所周知的挑剔,如果只改变一个代码行,可能会做一些完全不同的事情.

这是我们代码中的一个示例失败.代码实现了一种自旋锁,但没有使用原子.

bool operation_done;
void thread1() {
  while (!operation_done) {
    sleep();
  }
  // do something that depends on operation being done
}
void thread2() {
  // do the operation
  operation_done = true;
}
Run Code Online (Sandbox Code Playgroud)

这在调试模式下运行良好,但发布版本卡住了.调试显示thread1的执行从未离开循环,看着程序集,我们发现条件消失了; 循环只是无限的.

问题在于优化器意识到在其内存模型下,operation_done不可能在循环内发生变化(这可能是数据竞争),因此它"知道"一旦条件成立一次,它就永远是真的.

将operation_done的类型更改为atomic_bool(或实际上是一个预C++ 11编译器特定的等价物)修复了该问题.

  • @ gnasher729这不是C++中易变的意思,所以你知道......温室. (5认同)