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

C++ 是否有必要使用std::atomic来表示线程已完成执行?,c++,c++11,stdthread,stdatomic,C++,C++11,Stdthread,Stdatomic,我想检查std::thread是否已完成执行。通过搜索stackoverflow,我找到了以下解决此问题的方法。公认的答案是让工作线程在退出之前设置一个变量,并让主线程检查这个变量。以下是此类解决方案的一个最简单的工作示例: #include <unistd.h> #include <thread> void work( bool* signal_finished ) { sleep( 5 ); *signal_finished = true; } int m

我想检查
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();
}
#包括
#包括
无效工作(bool*信号完成){
睡眠(5);
*信号_finished=真;
}
int main()
{
bool thread_finished=false;
标准:螺纹工(工作和螺纹完成);
当(!线程_完成){
//做一些自己的工作,直到线程完成。。。
}
worker.join();
}
对公认答案发表评论的人声称,不能使用简单的
bool
变量作为信号,代码在没有内存屏障的情况下被破坏,使用
std::atomic
是正确的。我最初的猜测是这是错误的,一个简单的
bool
就足够了,但我想确保我没有遗漏什么以上代码是否需要一个
std::atomic
才能正确?


让我们假设主线程和辅助线程在不同套接字中的不同CPU上运行。我认为会发生的是,主线程从其CPU缓存中读取
thread\u finished
。当工作线程更新它时,缓存一致性协议负责将工作线程更改写入全局内存,并使主线程的CPU缓存失效,因此它必须从全局内存读取更新后的值。难道缓存一致性的全部目的不就是让上述代码正常工作吗?

这不好。优化器可以优化

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

假设它可以证明,“一些自己的工作”不会改变
线程\u finished

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

评论者是对的:简单的
bool
是不够的,因为将
thread\u finished
设置为
true
的线程的非原子写入可以重新排序

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

x = 42;
thread_finished = true;
当主线程看到
thread\u finished
设置为
true
时,它假定工作线程已完成。但是,当主线程检查
x
时,可能会发现它设置为错误的数字,因为上面的两次写入已重新排序

当然,这只是一个简单的例子来说明一般问题。对
线程_finished
变量使用
std::atomic
,会添加一个内存屏障,确保完成之前的所有写入操作。这修复了无序写入的潜在问题

另一个问题是可以优化对非易失性变量的读取,因此主线程永远不会注意到
thread\u finished
标志中的更改。

重要提示:使
线程\u完成
解决问题;事实上,volatile不应与线程一起使用-它用于处理内存映射硬件。

缓存一致性算法并非无处不在,也不是十全十美的。围绕
thread\u finished
的问题是,一个线程试图向其写入一个值,而另一个线程试图读取该值。这是一种数据竞争,如果访问未排序,则会导致未定义的行为。

使用原始
bool
是不够的

如果一个程序在不同的线程中包含两个冲突的操作,则该程序的执行包含一个数据竞争,其中至少一个操作不是原子的,并且两个操作都不在另一个线程之前发生。任何这样的数据竞争都会导致未定义的行为§1.10第21页

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

您的程序包含一个数据争用,其中工作线程写入bool,主线程从bool读取,但操作之间没有正式的关系


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

为什么不使用条件变量、信号量或autoresetevent来向线程发送信号?这就是这些东西的用途。如果编译器根据一次又一次地测试变量的值并最终修改应用程序的行为这一事实进行了一些优化,那么可能会出现问题。我从未见过这种情况发生,但我听说这可能是使用原子而不是简单bools的原因。@TonyTheLion:条件变量、信号量和事件用于等待(挂起线程)直到发生某些事情。他只是想测试是否发生了什么事情,所以原子bool更合适。有关相关问题:另请参阅此答案的注释:+1,以提及易失性和内存映射硬件。没有足够的人理解这一点:)
volatile
将阻止bool被优化,但它不会提供membar,也不会保证检查缓存与主内存的一致性的任何时间范围。atomic bool是正确的做法。“确保在完成之前完成所有写入”听起来像是
x=42;门巴;线程_finished=true
x=42;线程_finished=true;门巴-前者不能确保及时查看
线程完成的更新,后者可能会在
mem之前重新安排曝光
x = 42;
thread_finished = true;