C++中变量的修改顺序是如何定义的?

led*_*ter 5 c++ memory-model stdatomic c++20

我读过这个问答:与“(简单)发生在之前”相比,“强烈发生在之前”的意义是什么?

作者给出了一个有趣的评估的概述,该评估在 C++20 之前是不可能的,但显然从 C++20 开始是可能的:

.-- T3 y.store(3, seq_cst);                   --.                 (2)
|        |                                      | strongly
|        | sequenced before                     | happens
|        V                                      | before
|   T3 a = x.load(seq_cst); // a = 0    --.   <-'                 (3)
|                                         : coherence-
|                                         : ordered
|                                         : before
|   T1 x.store(1, seq_cst);             <-'   --. --.             (4)
|        |                                      |st |
|        | sequenced before                     |h  |
|        V                                      |b  |
| . T1 y.store(1, release);                   <-'   |             (x)
| |      :                                          | strongly
| |      : synchronizes with                        | happens
| |      V                                          | before
| > T2 b = y.fetch_add(1, seq_cst); // b = 1  --.   |             (1)
|        |                                      |st |
|        | sequenced before                     |h  |
|        V                                      |b  |
'-> T2 c = y.load(relaxed); // c = 3          <-' <-'
Run Code Online (Sandbox Code Playgroud)

右侧的数字表示可能的seq_cst顺序(为了方便起见,我添加了 (x);该行不参与 SC 顺序,因为它不是 SC 操作)。

我试图了解y这个例子中的修改顺序是什么,但我不知道如何确定它。y(或者这个评估有多种可能的修改顺序吗?..)

更一般地说,C++ 中原子变量的修改顺序是如何定义的?例如有这样的:https://en.cppreference.com/w/cpp/atomic/memory_order

写-写一致性:如果修改某个原子 M(写入)的评估 A 发生在修改 M 的评估 B 之前,则 A 在 M 的修改顺序中出现早于 B

所以看来修改顺序必须与写-写发生之前一致。

它是唯一定义修改顺序的东西吗?

在上面的例子中,AFAIU 在 (2) 和 (1) 之间没有发生之前;那么 的修改顺序中哪个是第一个y

模阶是y(x 1 2) (对于此评估)?

我相信这也可能有助于推理seq_cst顺序......

Pet*_*des 4

对象的修改顺序是线程在紧密循环运行中旋转时看到的顺序while(1) { y.load(relaxed); },并且碰巧看到每个更改。(并不是说这是实际观察它的有用方法,但每个对象都有自己的修改顺序,所有线程始终可以达成一致,就像在真实的 CPU 上一样,由于需要 MESI 独占所有权才能将存储提交到 L1d 缓存。或者查看它早期通过 POWER CPU 上的 SMT 线程之间的私有存储转发。)

一些随机事实:

  • 单个对象的修改顺序与一个线程内的程序顺序兼容,即使relaxed
  • 当线程看到一个带有加载的值后,它只能看到该值或修改顺序中后面的值,即使加载和存储是relaxed
  • 变量的观察值更改次数不能多于其他线程存储的次数。如果你有一堆来自一堆线程的商店,其中一个将是最后一个,这是所有读者(最终)都会看到的价值。当尘埃落定时,任何给定的读者都不会看到值来回变化,除了实际看到 mod 顺序中的后续存储之外。(参见标准中[intro.races]中的“一致性”规则)

我认为这个评估显示了实际有效的顺序,因此 mod 顺序y只是从上到下读取操作按(2) (x) (1)该顺序存储的值。(因为它使用了足够的seq_cst操作,所有线程都可以就顺序达成一致,并表明其他一些事情最终会在store(x)之后对 release store 进行排序。)seq_cst(2)

这个 eval 命令表示该(2)store 确实在 store 之前变得可见(x),因此(x)store 替换了它。

在 之前尘埃落定y.fetch_add (1),否则它会同步(2)而不是(x)

更正,显然他们表明 的 修改顺序是合法的,尽管(存储 3) 和最终(x) (1) (2)之间有不同风格的happens-before链。这样,该商店在 之后、之前对 T2 可见。(2)y.load()fetch_addload