与“(简单地)之前发生”相比,'strongly happens before' 的意义是什么?

What is the significance of 'strongly happens before' compared to '(simply) happens before'?

该标准定义了几个 'happens before' 关系,它们将旧的 'sequenced before' 扩展到多个线程:

[intro.races]

11 An evaluation A simply happens before an evaluation B if either

(11.1) — A is sequenced before B, or
(11.2) — A synchronizes with B, or
(11.3) — A simply happens before X and X simply happens before B.

[Note 10: In the absence of consume operations, the happens before and simply happens before relations are identical. — end note]

12 An evaluation A strongly happens before an evaluation D if, either

(12.1) — A is sequenced before D, or
(12.2) — A synchronizes with D, and both A and D are sequentially consistent atomic operations ([atomics.order]), or
(12.3) — there are evaluations B and C such that A is sequenced before B, B simply happens before C, and C is sequenced before D, or
(12.4) — there is an evaluation B such that A strongly happens before B, and B strongly happens before D.

[Note 11: Informally, if A strongly happens before B, then A appears to be evaluated before B in all contexts. Strongly happens before excludes consume operations. — end note]

(大胆的矿)

两者之间的区别似乎非常微妙。 'Strongly happens before' 对于匹配对或发布-获取操作永远不会成立(除非两者都是 seq-cst),但它仍然在某种程度上尊重发布-获取同步,因为操作顺序在发布之前 'strongly happen before' 在匹配获取之后排序的操作。

为什么这种差异很重要?

'Strongly happens before'是在C++20引入的,在C++20之前,'simply happens before'曾经被称为'strongly happens before'。为什么引入它?

[atomics.order]/4表示所有seq-cst操作的总顺序与'strongly happens before'.

一致

是不是和'simply happens before'不一致?如果是,为什么不呢?


我忽略了普通的 'happens before',因为它与 'simply happens before' 的不同之处仅在于它对 memory_order_consume 的处理,temporarily discouraged 的使用,因为显然大多数(全部?)主要编译器将其视为 memory_order_acquire.

我已经看到了 this Q&A,但是它并没有解释为什么 'strongly happens before' 存在,也没有完全说明它的含义(它只是声明它不尊重发布-获得同步,但情况并非完全如此。


找到 the proposal 介绍 'simply happens before'.

我不是很明白,但是解释如下:

这是我目前的理解,可能不完整或不正确。验证将不胜感激。


C++20 将 strongly happens before 重命名为 simply happens before,并为 strongly happens before 引入了一个新的、更宽松的定义,减少了顺序。

Simply happens before 用于推断代码中是否存在数据竞争。 (实际上那将是普通的 'happens before',但是在没有使用操作的情况下两者是等价的,标准不鼓励使用它,因为大多数(所有?)主要编译器将它们视为获取。)

较弱的strongly happens before用于推理seq-cst操作的全局顺序


Lahav 等人在提议 P0668R5: Revising the C++ memory model, which is based on the paper Repairing Sequential Consistency in C/C++11 中引入了此更改(我没有完全阅读)。

提案解释了进行更改的原因。长话短说,大多数编译器在 Power 和 ARM 架构上实现原子的方式在极少数情况下被证明是不一致的,修复编译器会产生性能成本,因此他们修复了标准。

如果您在同一个原子变量上将 seq-cst 操作与获取-释放操作混合使用,更改只会影响您(即,如果获取操作从 seq- cst 存储,或 seq-cst 操作从发布存储读取值)。

如果您不以这种方式混合操作,那么您不会受到影响(即可以将 simply happens beforestrongly happens before 视为等效)。

变化的要点是一个seq-cst操作和相应的acquire/release操作之间的同步不再影响这个特定的seq-cst操作在全局seq中的位置-cst 命令,但同步本身仍然存在。

这使得此类 seq-cst 操作的 seq-cst 顺序非常没有实际意义,请参见下文。


该提案给出了以下示例,我将尝试解释我对它的理解:

atomic_int x = 0, y = 0;
int a = 0, b = 0, c = 0;
// Thread 1
x.store(1, seq_cst);
y.store(1, release);
// Thread 2
b = y.fetch_add(1, seq_cst); // b = 1 (the value of y before increment)
c = y.load(relaxed); // c = 3
// Thread 3
y.store(3, seq_cst);
a = x.load(seq_cst); // a = 0

评论指出了此代码可以执行的方式之一,标准曾经禁止(在此更改之前),但实际上可以在受影响的体系结构上发生。

执行过程如下:

.-- 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);                   <-'   |
| |      :                                          | 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          <-' <-'

其中:

  • 右边括号中的数字表示全局 seq-cst 顺序。

  • 左侧的箭头显示值如何在某些加载和存储之间传播。

  • 中间的箭头显示:

    • 'Sequenced before',好的旧单线程求值顺序。
    • 'Synchronizes with',release-acquire同步(seq-cst loads算作acquire操作,seq-cst stores算作release操作)

    这两个加起来就是'simply happens before'.

  • 右边的箭头是以中间的箭头为基础的,表示:

    • 新定义的'strongly happens before'关系。

    • 'Coherence-ordered before',本提案中引入的新关系,仅用于定义全局 seq-cst 顺序,显然不强加同步(与发布获取操作不同)。

      似乎它包括除 'simply happens before' 以外的所有影响全局 seq-cst 顺序的内容。在这种情况下,这是常识,如果加载没有看到存储写入的值,那么加载先于存储。

    全局seq-cst顺序与两者一致

请注意,在这张图片上,在 b = y.fetch_add(1, seq_cst); 之前没有发生任何强烈的事情,所以在全局 seq-cst 顺序中没有任何必须在它之前的东西,所以 可以移动它一直到 seq-cst 顺序的开头,这就是这种情况最终发生的情况,即使它读取了后来(按此顺序)操作产生的值。