18

我想检查 astd::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变量作为信号,代码在没有内存屏障的情况下被破坏并且使用std::atomic<bool>是正确的。我最初的猜测是这是错误的,一个简单bool的就足够了,但我想确保我没有遗漏任何东西。上面的代码是否需要astd::atomic<bool>才能正确?

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

4

4 回答 4

23

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

评论者是对的:简单是不够的,因为来自设置为bool的线程的非原子写入可以重新排序。thread_finishedtrue

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

x = 42;
thread_finished = true;

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

当然这只是一个简化的例子来说明一般问题。使用std::atomic你的thread_finished变量会增加一个内存屏障,确保在它完成之前的所有写入。这解决了乱序写入的潜在问题。

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


重要提示:使您的thread_finishedvolatile无法解决问题;事实上,volatile 不应该与线程一起使用——它旨在与内存映射硬件一起使用。

于 2013-01-16T19:04:05.667 回答
7

使用 rawbool是不够的。

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

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

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

There are a number of different ways to avoid the data race, including using std::atomic<bool> with appropriate memory orderings, using a memory barrier, or replacing the bool with a condition variable.

于 2013-01-16T21:43:04.583 回答
3

不行。优化器可以优化

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

到:

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

假设它可以证明,“一些自己的工作”不会改变thread_finished

于 2013-01-16T19:03:44.010 回答
2

缓存一致性算法并非无处不在,也不是完美的。周围的问题thread_finished是一个线程试图向它写入一个值,而另一个线程试图读取它。这是一场数据竞争,如果访问没有排序,则会导致未定义的行为。

于 2013-01-16T19:04:16.330 回答