是否有必要使用std :: atomic来表示线程已完成执行?

Rob*_*ger 16 c++ c++11 stdthread stdatomic

我想检查一下是否std::thread已完成执行.搜索stackoverflow我发现以下问题解决了这个问题.接受的答案建议让工作线程在退出之前设置变量并让主线程检查该变量.以下是此类解决方案的最小工作示例:

#include <unistd.h>
#include <thread>

void work( bool* signal_finished ) {
  sleep( 5 );
  *signal_finished = true;
}

int main()
{
  bool thread_finished = false;
  std::thread worker(work, &thread_finished);

  while ( !thread_finished ) {
    // do some own work until the thread has finished ...
  }

  worker.join();
}
Run Code Online (Sandbox Code Playgroud)

对已接受的答案发表评论的人声称,不能使用简单的bool变量作为信号,代码在没有内存屏障的情况下被破坏并且使用std::atomic<bool>是正确的.我最初的猜测是这是错误的,简单bool就足够了,但我想确保我没有错过任何东西.以上代码是否需要a std::atomic<bool>才能正确?

假设主线程和worker正在不同套接字中的不同CPU上运行.我认为会发生的是,主线程thread_finished从其CPU的缓存中读取.当worker更新它时,缓存一致性协议负责将worker更改为全局内存并使主线程的CPU缓存无效,因此它必须从全局内存中读取更新的值.缓存一致性的全部要点是不能像上面的代码一样工作吗?

das*_*ght 21

对已接受的答案发表评论的人声称,不能使用简单的bool变量作为信号,代码在没有内存屏障的情况下被破坏,并且使用std :: atomic将是正确的.

评论者是正确的:简单bool是不够的,因为从将线程非原子写入thread_finishedtrue可以重新排序.

考虑一个线程,它将一个静态变量设置x为一些非常重要的数字,然后指示它的退出,如下所示:

x = 42;
thread_finished = true;
Run Code Online (Sandbox Code Playgroud)

当主线程看到thread_finished设置为时true,它假定工作线程已完成.但是,当您的主线程检查时x,它可能会发现它设置为错误的数字,因为上面的两个写入已被重新排序.

当然,这仅是用于说明一般问题的简化示例.使用std::atomic您的thread_finished变量增加了内存屏障,确保它完成之前,所有的写操作.这解决了无序写入的潜在问题.

另一个问题是可以优化对非易失性变量的读取,所以主线程永远不会注意到thread_finished标志的变化.


重要提示:使你的thread_finished挥发是不会去解决问题; 事实上,volatile不应与线程一起使用 - 它适用于内存映射硬件.

  • `volatile`将阻止bool被优化,但它不会提供membar,或保证任何时间帧来检查与主内存的缓存一致性.原子布尔是正确的举动. (5认同)

bam*_*s53 7

使用原始bool是不够的.

程序的执行包含数据竞争,如果它在不同的线程中包含两个冲突的动作,其中至少有一个不是原子的,并且都不会在另一个之前发生.任何此类数据争用都会导致未定义的行为.§1.10p21

如果其中一个修改内存位置(1.7)而另一个访问或修改相同的内存位置,则两个表达式评估会发生冲突.§1.10p4

你的程序包含一个数据竞争,其中工作线程写入bool并且主线程从中读取,但是操作之间没有正式发生的关系.

有许多不同的方法可以避免数据竞争,包括使用std::atomic<bool>适当的内存排序,使用内存屏障,或用条件变量替换bool.