为什么这会导致死锁?
Why this may lead to a deadlock?
请参阅从 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4507.pdf 第 12 页获取的以下代码:
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) { }
});
本例附有评论:
The above example depends on the order of execution of the iterations,
and is therefore undefined (may deadlock).
但我不明白为什么会导致死锁。据我所知,虽然内存顺序指定为 std::memory_order_relaxed
,但这只是关于线程看到另一个线程所做的一些更改的时间,所以最终(在可能无限量的时间之后)应该注意到更改通过阅读线程。
谁能解释一下?谢谢!
The threads are reading new value while they are waiting, so at some point they should see the changes they made, as I understood.
不,不是。
宽松的内存顺序并不意味着"things from other threads may eventually be ordered before this read."它意味着"things from other threads are not ordered before this read."也就是说,任何其他写入可能永远不会变得可见。
因此 UB。
现在在实际系统上,宽松的内存顺序(特别是对于无锁原子)完全有可能仍然使其他线程的修改可见。但就标准而言,这就是UB。一个过分热心的编译器确实可以将您的代码编译成硬 while(true);
,从不费心从变量中读取,因为它知道它无法看到其他线程的更改。
x.fetch_add
使增量成为原子但
std::memory_order_relaxed
不是线程的同步点,因此任何两个线程都可以同时拥有 1 个。因为读-修改-写在没有同步的情况下开始,所以得不到想要的 x 值。
示例代码可能死锁的原因与memory_order_relaxed
的使用无关。
对原子变量的更改将对另一个核心可见,如果不可见(根据标准,它应该变为可见的),
它与内存排序无关,内存排序仅用于指定 other 内存操作相对于原子操作的排序方式。
link 引用的文档中给出的示例可能会死锁,因为显然不能保证执行是真正并发的。在后来的草案(N4640)中,文本已被修改:
... depends on the order of execution of the iterations, and will not terminate if both iterations are executed sequentially on the same thread of execution.
这就是全部内容;如果两次迭代都按顺序执行,那么第一次迭代会一直在一个永远不会改变的值上旋转。
请参阅从 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4507.pdf 第 12 页获取的以下代码:
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) { }
});
本例附有评论:
The above example depends on the order of execution of the iterations, and is therefore undefined (may deadlock).
但我不明白为什么会导致死锁。据我所知,虽然内存顺序指定为 std::memory_order_relaxed
,但这只是关于线程看到另一个线程所做的一些更改的时间,所以最终(在可能无限量的时间之后)应该注意到更改通过阅读线程。
谁能解释一下?谢谢!
The threads are reading new value while they are waiting, so at some point they should see the changes they made, as I understood.
不,不是。
宽松的内存顺序并不意味着"things from other threads may eventually be ordered before this read."它意味着"things from other threads are not ordered before this read."也就是说,任何其他写入可能永远不会变得可见。
因此 UB。
现在在实际系统上,宽松的内存顺序(特别是对于无锁原子)完全有可能仍然使其他线程的修改可见。但就标准而言,这就是UB。一个过分热心的编译器确实可以将您的代码编译成硬 while(true);
,从不费心从变量中读取,因为它知道它无法看到其他线程的更改。
x.fetch_add
使增量成为原子但
std::memory_order_relaxed
不是线程的同步点,因此任何两个线程都可以同时拥有 1 个。因为读-修改-写在没有同步的情况下开始,所以得不到想要的 x 值。
示例代码可能死锁的原因与memory_order_relaxed
的使用无关。
对原子变量的更改将对另一个核心可见,如果不可见(根据标准,它应该变为可见的), 它与内存排序无关,内存排序仅用于指定 other 内存操作相对于原子操作的排序方式。
link 引用的文档中给出的示例可能会死锁,因为显然不能保证执行是真正并发的。在后来的草案(N4640)中,文本已被修改:
... depends on the order of execution of the iterations, and will not terminate if both iterations are executed sequentially on the same thread of execution.
这就是全部内容;如果两次迭代都按顺序执行,那么第一次迭代会一直在一个永远不会改变的值上旋转。