英文:
How sequentially consistent ordering in c++ define a total order?
问题
I'll provide a translation of the text you provided:
我正在阅读来自https://en.cppreference.com/w/cpp/atomic/memory_order#Sequentially-consistent_ordering 的以下示例。我很难理解以下情况下 assert(z.load() != 0);
会失败:
- 何时会失败
- 为什么使用 memory_order_seq_cst 而不是 memory_order_ack_rel 会使 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); // 永远不会发生
}
据我了解,无论是 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
中的一个变为 true。然而,根据 cppreference 上的说明:
此示例演示了需要顺序排序的情况。任何其他排序都可能触发断言,因为线程 c 和 d 可能以相反的顺序观察到对原子变量 x 和 y 的更改。
我不明白这是如何可能的。为什么对 x
和 y
的更改会以相反的顺序发生?
此外,我想知道使用 memory_order_seq_cst 如何解决这个问题。它是否强制要求在 read_x_then_y
中执行的 x.load
必须在 y.load
之前执行?
请注意,我已经省略了代码部分,只提供了翻译的内容。如果您需要进一步的解释或回答,请告诉我。
英文:
I am reading the following example from https://en.cppreference.com/w/cpp/atomic/memory_order#Sequentially-consistent_ordering. And I have a hard time understanding
- under what situation
assert(z.load() != 0);
will fail. - why does using memory_order_seq_cst over memory_order_ack_rel make z never be 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
}
As far as I under, for either read_x_then_y
or read_y_then_x
, they could observe a state among:
x = true
andy = false
x = false
andy = true
x = true
andy = true
x = false
andy = false
The first 2 cases make z = 1
(eventually 2), the third case makes z = 2
, and the last case makes read_x_then_y
and read_y_then_x
wait until one of `x' and 'y' become true. However, according to the cppreference
> This example demonstrates a situation where sequential ordering is necessary. Any other ordering may trigger the assert because it would be possible for the threads c and d to observe changes to the atomics x and y in opposite order.
I don't understand how is that possible. How would the changes to x and y be in the opposite order?
In addition, I am wondering how would the use of memory_order_seq_cst solves the problem. Is it forcing the x.load
in read_x_then_y
must be executed before y.load
?
答案1
得分: 2
我建议阅读我的《内存顺序介绍》,其中解释了大部分内容。
如何使 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
。
英文:
I suggest reading my introduction to memory orders, which explains most of this.
> How would the changes to x and y be in the opposite order?
Because by default (in absence of seq_cst
) threads only agree on the order of operations on each individual atomic variable. They can disagree on how those operations are interleaved.
So if you remove seq_cst
, all threads will merely agree that x
and y
start as false
and are changed to true
, but they can disagree which of them is changed first.
> In addition, I am wondering how would the use of memory_order_seq_cst solves the problem.
Because for seq_cst
operations, all threads agree on how they are interleaved. In other words, there's a single total order (that all threads agree on) in which all seq_cst
operations happen.
So if thread read_x_then_y()
sees x == true && y == false
and does z++
, then we've estabilished that x = true
"happens before" y = true
, so the thread read_y_then_x()
has to agree with this observation, and can't see x == false && y == true
.
答案2
得分: 0
以下是已翻译的内容:
对于read_x_then_y
:
void read_x_then_y()
{
while (!x.load(std::memory_order_seq_cst))
;
if (y.load(std::memory_order_seq_cst)) {
++z;
}
}
第一个循环等待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
在这种情况下,会触发断言。
答案是这是顺序一致性排序提供的保证。所有这些原子操作共享一个单一的总修改顺序。要么对x
的存储发生在首位,要么对y
的存储发生在首位,按照这个顺序。所有加载将在该顺序上达成一致。
发布存储和获取加载提供了更少的保证:同一线程中的发布存储之前的所有存储对其他线程中的获取加载可见。因此,在加载x
之后,由write_x
存储的任何变量都可以由read_x_then_y
加载。当然,没有这样的存储。
英文:
For read_x_then_y
:
void read_x_then_y()
{
while (!x.load(std::memory_order_seq_cst))
;
if (y.load(std::memory_order_seq_cst)) {
++z;
}
}
The first loop waits until x
is loaded as true
. Then ++z
is executed if y
is loaded as true
. At this point, y
could be either true
or false
. Both executions are possible.
Likewise, for read_y_then_x
, both executions are possible.
What prevents the following?
read_x_then_y
loadsx
astrue
buty
asfalse
read_y_then_x
loadsy
astrue
butx
asfalse
In this case, the assert would trigger.
The answer is that this is the guarantee sequentially consistent ordering provides. All such atomic operations share a single total modification order. Either the store to x
happens first, or the store to y
happens first, in this order. All loads will agree on that order.
Release stores and acquire loads provide much less: any stores before the release stores in the same thread are visible to acquire loads in other threads. So all variables stored to by write_x
before the store, would be loadable by read_x_then_y
after x
is loaded. Of course, there are no such stores.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论