另一个线程在被加入主线程后,访问主线程中的变量是否安全?

6

这个是否线程安全?

int x = 0;
std::thread([&]{ x = 1; }).join();
std::cout << x;

变量x在两个线程中被访问,没有使用原子操作或锁。然而,对join()的调用强制访问x是顺序的。

这里需要内存屏障吗?


3
无数据竞争 = 无需此类操作。 - user2485710
1个回答

11

是的,那个特定的代码片段是线程安全的;不需要使用屏障或锁。

这是与您的代码相关的事件时间轴:

thread 1 
--------
   |
  int x = 0;
  (write 0 to x)
   |
  std::thread                thread 2
  (start thread 2) --------> --------
   |                            |
  join();                      x = 1;
  (thread 1 suspended)         (write 1 to x)
   .                            |
   .                           thread 2 returns
   .                            |
  (thread 1 resumes) <-------   x
   |
  std::cout << x;
  (read from x)
   |
  thread 1 returns
   |
   x

正如您所看到的,x 没有被超过一个线程访问。事实上,使用 join() 可以使所有对 x 的访问按顺序发生,就像您所推测的那样。在代码中,join() 提供了同步,而不是从锁中获取的同步。
基本上,您拥有的是一个示例,展示了如何通过多线程实现零并发。
当然,这仅在您提供的代码片段中创建线程后立即调用 join() 的情况下才成立。如果您有类似以下代码的内容:
int x = 0;
std::thread t([&]{ x = 1; });
std::cout << x;
t.join(); // Move join() call here

相应的时间轴可能如下所示:

thread 1 
--------
   |
  int x = 0;
  (write 0 to x)
   |
  std::thread                thread 2
  (start thread 2) --------> --------
   |                            |
  std::cout << x;              x = 1;
  (read from x)                (write 1 to x)    <-- PROBLEM!
   |                            |
  join();                       |
  (thread 1 suspended)          |
   .                            |
   .                           thread 2 returns
   .                            |
  (thread 1 resumes) <-------   x
   |
  thread 1 returns
   |
   x

以这种方式更改join()的顺序会引入竞争条件。


2
这是一种非常全面的表达“是”的方式。 - Jonathan Leffler
1
我想从现在开始使用那种图表风格来发布有关线程的内容。 - Prime
除非join()有一些特殊的保证,否则主线程在join()之后使用缓存的x值并打印旧结果,对吗? - mitchnull
@mitchnull:你的抽象层次太低了。就C++而言,只有一个x变量,两个线程都会读取和/或写入它(第二个线程当然是通过引用来进行的)。变量x实际上可能是在不同处理器上的两个副本,但处理器的工作是确保更改传播到所有副本(例如,参见MESI协议)。然而,这样的实现细节与语言无关。 - In silico

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接