为什么这会导致僵局?

时间:2017-03-13 00:54:32

标签: c++ multithreading c++17

请参阅第{12页} http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4507.pdf带来的以下代码:

using namespace std::experimental::parallel;
std::atomic<int> x = 0;
int a[] = {1,2};
for_each(par, std::begin(a), std::end(a), [&](int n) {
  x.fetch_add(1, std::memory_order_relaxed);
  // spin wait for another iteration to change the value of x
  while (x.load(std::memory_order_relaxed) == 1) { }
});

此示例中附有评论:

  

以上示例取决于迭代的执行顺序,   因此未定义(可能死锁)。

但我不明白为什么它可能导致僵局。据我所知,尽管内存顺序指定为std::memory_order_relaxed,但这只是一个线程看到另一个线程所做的某些更改的时间,所以最终(在可能无限制的时间之后),无论如何应该注意到更改通过阅读线程。

有人可以解释一下吗?谢谢!

3 个答案:

答案 0 :(得分:3)

示例代码死锁的原因与memory_order_relaxed的使用无关。

对原子变量的更改将变为对另一个核心可见,如果不是(根据标准应该变得可见), 它与内存或内存无关,后者仅用于指定其他内存操作如何针对原子操作进行排序。

链接所引用的文档中给出的示例可能会死锁,因为显然不能保证执行是真正并发的。在后来的草案(N4640)中,案文已经修订:

  

...取决于迭代执行的顺序,如果两个迭代在同一执行线程上顺序执行,则不会终止。

这就是这就是全部;如果两个迭代都是按顺序执行的,那么第一个迭代将继续旋转一个永不改变的值。

答案 1 :(得分:2)

  

线程在等待时正在读取新值,所以在某些时候他们应该看到他们所做的更改,正如我所理解的那样。

不,它没有。

轻松的记忆顺序并不意味着&#34;来自其他线程的东西最终可能会在此次阅读之前被订购。&#34;这意味着&#34;来自其他线程的东西在读取之前没有被订购。&#34;也就是说,任何其他写入都可能永远变得可见。

因此UB。

现在在实际系统中,放松的记忆顺序(特别是对于无锁原子)完全可能仍然可以使其他线程&#39;修改可见。但就标准而言,这是UB。一个过于热心的编译器确实可以将你的代码编译成一个硬while(true);,从不打扰从变量读取,因为它知道它无法看到来自其他线程的变化。

答案 2 :(得分:1)

x.fetch_add

使增量成为原子但

std::memory_order_relaxed

不是线程的同步点,因此任何两个线程可以同时拥有1个。因为read-modify-write在没有同步的情况下启动,所以不会获得所需的x值。