我正在从www.example.com上阅读下面的例子https://en.cppreference.com/w/cpp/atomic/memory_order#Sequentially-consistent_ordering。我很难理解
- 在什么情况下
assert(z.load() != 0);
会失败。 - 为什么在memory_order_ack_rel上使用memory_order_seq_cst会使z永远不为0?
#include <thread>
#include <atomic>
#include <cassert>
std::atomic<bool> x = {false};
std::atomic<bool> y = {false};
std::atomic<int> z = {0};
void write_x()
{
x.store(true, std::memory_order_seq_cst);
}
void write_y()
{
y.store(true, std::memory_order_seq_cst);
}
void read_x_then_y()
{
while (!x.load(std::memory_order_seq_cst))
;
if (y.load(std::memory_order_seq_cst)) {
++z;
}
}
void read_y_then_x()
{
while (!y.load(std::memory_order_seq_cst))
;
if (x.load(std::memory_order_seq_cst)) {
++z;
}
}
int main()
{
std::thread a(write_x);
std::thread b(write_y);
std::thread c(read_x_then_y);
std::thread d(read_y_then_x);
a.join(); b.join(); c.join(); d.join();
assert(z.load() != 0); // will never happen
}
就我所知,对于read_x_then_y
或read_y_then_x
,他们可以观察到以下状态:
x = true
和y = false
x = false
和y = true
x = true
和y = true
x = false
和y = false
前两种情况使z = 1
(最终为2),第三种情况使z = 2
,最后一种情况使read_x_then_y
和read_y_then_x
等待,直到“x”和“y”之一变为真。然而,根据cppreference
这个例子演示了一种需要顺序排序的情况。任何其他排序都可以触发Assert,因为线程c和d可能以相反的顺序观察原子x和y的变化。
我不明白这怎么可能。x和y的变化如何以相反的顺序进行?
另外,我想知道如何使用memory_order_seq_cst解决这个问题。是否强制read_x_then_y
中的x.load
必须在y.load
之前执行?
2条答案
按热度按时间ddrv8njm1#
我建议阅读我的introduction to memory orders,它解释了大部分内容。
x和y的变化如何以相反的顺序进行?
因为默认情况下(在没有
seq_cst
的情况下)线程只同意对每个原子变量的操作顺序。他们可能会对这些操作的交叉方式产生分歧。因此,如果删除
seq_cst
,所有线程只会同意x
和y
从false
开始并更改为true
,但它们可以不同意先更改其中的哪一个。另外,我想知道如何使用memory_order_seq_cst解决这个问题。
因为对于
seq_cst
操作,所有线程都同意如何交错。换句话说,所有seq_cst
操作都有一个总顺序(所有线程都同意)。因此,如果线程
read_x_then_y()
看到x == true && y == false
并执行z++
,那么我们已经建立了x = true
“发生在”y = true
之前,因此线程read_y_then_x()
必须同意这个观察结果,并且不能看到x == false && y == true
。s3fp2yjn2#
对于
read_x_then_y
:第一个循环等待,直到
x
作为true
加载。如果y
作为true
加载,则执行++z
。此时,y
可以是true
或false
。两种执行都是可能的。同样,对于
read_y_then_x
,两种执行都是可能的。什么可以防止以下情况?
read_x_then_y
将x
加载为true
,但将y
加载为false
read_y_then_x
将y
加载为true
,但将x
加载为false
在这种情况下,Assert将触发。
答案是,这是顺序一致排序提供的保证。所有这样的原子操作共享单个总修改顺序。按照这个顺序,要么先存储到
x
,要么先存储到y
。所有货物将同意该订单。释放存储和获取加载提供的功能要少得多:相同线程中的释放存储之前的任何存储对于获取其它线程中的加载是可见的。因此,在存储之前由
write_x
存储的所有变量都可以在加载x
之后由read_x_then_y
加载。当然,没有这样的商店。