与(简单地)发生在'之前相比,'强烈发生在'之前有什么意义呢?

该标准定义了几个"在此之前发生"关系,这些关系将良好的旧"在此之前排序"扩展到多个线程:

[intro.races]

11评估A仅发生在评估B之前,如果出现以下情况

(11.1)-A排在B之前,或
(11.2)-A与B同步,或
(11.3)-A在X之前,X在B之前。

[注10:在没有使用操作的情况下,在关系相同之前和简单地在关系相同之前发生。-结束语]

12评估A强烈发生在评估D If之前,

(12.1)-A在D之前排序,或
(12.2)-A与D同步,A和D都是顺序一致的原子操作 ([Atomics.order])或
(12.3)-有评估B和C,A排在B之前,B简单地排在C之前,C排在D之前,或
(12.4)-有一个评估B,即A强烈出现在B之前,而B强烈出现在D之前。

[注11:非正式地说,如果A强烈出现在B之前,那么在所有上下文中,A似乎都在B之前被评估。强烈发生在排除消耗操作之前。-结束语]

(粗体矿)

两者之间的区别似乎非常微妙。对于匹配对或释放-获取操作(除非两者都是seq-cst),"严格发生在此之前"永远不会为真,但在某种程度上仍然尊重释放-获取同步,因为在释放之前排序的操作"强烈发生在"匹配获取之后的操作之前。

此差异为什么重要?

在C++20中引入了"之前发生的强烈事件",而在C++20之前,"简单发生在此之前"通常被称为"之前发生的强烈事件"。为什么要引入它?

[atomics.order]/4表示所有seq-cst操作的总顺序与‘强烈发生在此之前’一致。

这是否意味着它与"以前简单发生的事情"不一致?如果是,原因何在?


我忽略了简单的"发生在此之前",因为它与"简单发生在此之前"的不同之处只在于它对memory_order_consume的处理,它的用法是temporarily discouraged,因为显然大多数(all?)主要编译器将其视为memory_order_acquire

我已经看过this Q&A,但它没有解释为什么‘强烈发生在’存在之前‘,也没有完全说明它的含义(它只是声明它不尊重释放-获取同步,事实并非如此)。


发现the proposal引入的"仅发生在此之前"。

我不完全理解,但解释如下:

  • "之前强烈发生"是"简单发生在之前"的弱化版本。
  • 只有当seq-cst与aqc-rel在同一变量上混合使用时(我认为,这意味着获取加载从seq-cst存储中读取值,或者seq-cst加载从释放存储中读取值),才能观察到差异。但对我来说,将两者混合的确切效果还不清楚。

解决方案

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


C++20已将strongly happens before重命名为simply happens before,并为strongly happens before引入了更宽松的新定义,从而降低了排序。

Simply happens before用于推断代码中是否存在数据竞争。(实际上,这显然是"以前发生的",但如果没有消费操作,这两种操作是等价的,标准不鼓励使用消费操作,因为大多数(所有?)主要编译器将它们视为获取。)

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


此更改是在提案P0668R5: Revising the C++ memory model中引入的,该提案基于Lahav等人的论文Repairing Sequential Consistency in C/C++11(我没有完全阅读)。

该提案解释了做出更改的原因。长话短说,大多数编译器在Power和ARM体系结构上实现原子的方式在极少数边缘情况下被证明是不一致的,而且修复编译器有性能成本,因此他们改为修复标准。

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

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

更改的要点是seq-cst操作与相应的获取/释放操作之间的同步不再影响此特定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          <-' <-'

其中:

  • 右侧的括号数字显示全局序号-CST顺序。

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

  • 中间的箭头显示:

    • 'Sequenced before',很老的单线程求值顺序。
    • ‘同步于’,释放-获取同步(seq-cst加载计数为获取操作,seq-cst存储计数为释放操作)。

    这两个加在一起构成"简单地发生在此之前"。

  • 右侧的箭头基于中间的箭头,它们显示:

    • 新定义的"强发生在此之前"关系。

    • 'Coherence-ordered before',本方案中引入的新关系,仅用于定义全局seq-cst顺序,显然不强制同步(不同于Release-Acquisition操作)。

      它似乎包括了影响全球SEQ-CST秩序的所有事情,而不是"之前发生的事情"。在本例中,如果加载看不到存储写入的值,则加载将先于存储进行,这是常识。

    全局序号-CST顺序与两者一致。

请注意,在此图中,b = y.fetch_add(1, seq_cst);之前没有强烈的事件发生,因此在全局seq-cst顺序中没有必须在其之前的任何内容,因此可以将其向上移动到seq-cst顺序的开始,这就是最终发生的情况,即使它读取后面(按此顺序)操作生成的值。

相关文章