关于c ++ 11:C ++内存模型 – 这个例子是否包含数据竞争?

C++ memory model - does this example contain a data race?

我正在阅读Bjarne Stroustrup的C++ 11 FAQ,而且我很难理解内存模型中的一个例子。

他给出了以下代码片段:

1
2
3
// start with x==0 and y==0
if (x) y = 1; // thread 1
if (y) x = 1; // thread 2

常见问题解答说这里没有数据竞赛。我不明白。内存位置x由线程1读取,由线程2写入,而不进行任何同步(y也是如此)。这是两个访问,其中一个是写。这不是数据竞赛的定义吗?

此外,它还说:"当前的C++编译器(我知道)给出了一个正确答案。"这个正确答案是什么?根据一个线程的比较发生在另一个线程的写入之前还是之后(或者如果另一个线程的写入对读取线程甚至可见),答案是否会有所不同?


1
2
3
// start with x==0 and y==0
if (x) y = 1; // thread 1
if (y) x = 1; // thread 2

因为x和y都不是真的,另一个也不会设置为真。无论指令的执行顺序如何,(正确的)结果总是x保持0,y保持0。


The memory location x is ... written to by thread 2

真的吗?你为什么这么说?

如果y为0,那么线程2不会将x写入。y从0开始。同样,除非在线程1运行之前,y不为零,否则x不能为非零,而这是不可能发生的。这里的一般观点是,不执行的条件写入不会导致数据争用。

不过,这是内存模型的一个重要事实,因为不知道线程的编译器可以(假设y不是易失性的)将代码if (x) y = 1;转换为int tmp = y; y = 1; if (!x) y = tmp;。然后会有一场数据竞赛。我无法想象它为什么要做这种精确的转换,但这并不重要,关键是非线程环境的优化器可以做一些违反线程内存模型的事情。因此,当Stroustrup说他知道的每一个编译器都给出正确的答案(恰好在C++ 11的线程模型下),那是一个关于C++ 11线程的编译器的准备的非平凡的声明。

更现实的if (x) y = 1的转变将是y = x ? 1 : y;。我相信在您的例子中,这将导致数据竞争,并且对于分配标准y = y中没有特殊的处理,使得在另一个线程中对y的读取执行未排序是安全的。你可能会发现很难想象它在哪个硬件上不工作,不管怎样,我可能错了,这就是为什么我使用了上面的另一个例子,它不太现实,但有一个明显的数据竞争。


必须对写入进行完全排序,因为在其他线程首次将1写入变量之前,任何线程都不能写入变量xy。换言之,您基本上有三种不同的场景:

  • 线程1开始写入y,因为x是在if语句之前的某个时间点写入的,然后如果线程2随后出现,它会将x的相同值写入1,并且不会改变它以前的1值。
  • 线程2开始写入x,因为yif语句之前的某个时间点发生了更改,然后线程1将写入y,如果以后的值与1的值相同。
  • 如果只有两个线程,那么会跳过if语句,因为xy保持为0。

  • 两种写入都不会发生,因此没有竞争。X和Y都保持为零。

    (这是关于幽灵写作的问题。假设有一个线程在检查条件之前进行了推测性的写入,然后尝试在之后进行更正。这会破坏另一条线,所以不允许这样做。)


    内存模型设置代码和数据区域的可支持大小,在比较链接源代码之前,需要指定内存模型,即可以设置数据和代码的大小限制。