16

在 C++ 草案标准中多次使用短语“强烈发生在之前”。

例如:终止 [basic.start.term]/5

如果具有静态存储持续时间的对象的初始化完成强烈发生在调用 std​::​atexit 之前(参见 [support.start.term]),则对函数的调用传递给 std​::​atexit在调用对象的析构函数之前排序。如果对 std​::​atexit 的调用强烈发生在具有静态存储持续时间的对象的初始化完成之前,则对对象的析构函数的调用在调用传递给 std​::​atexit 的函数之前排序. 如果对 std​::​atexit 的调用强烈发生在对 std​::​atexit 的另一次调用之前,则对传递给第二个 std​::​atexit 调用的函数的调用在传递给第一个 std​::​atexit 调用。

并在 数据竞赛 [intro.races]/12中定义

评估 A 强烈地发生在评估 D 之前,如果,

(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 之前。

[注:非正式地,如果 A 强烈地发生在 B 之前,那么在所有情况下 A 似乎在 B 之前被评估。强烈发生在排除消费操作之前。——尾注]

为什么要引入“之前发生的强烈事件”?直观地说,它与“发生在之前”有什么区别和关系?

注释中的“A 似乎在所有情况下都在 B 之前被评估”是什么意思?

(注意:这个问题的动机是 Peter Cordes 在这个答案下的评论。)

附加标准报价草案(感谢 Peter Cordes)

顺序和一致性 [atomics.order]/4

在所有 memory_order​::​seq_cst 操作(包括栅栏)上都有一个总顺序 S,它满足以下约束。首先,如果 A 和 B 是 memory_order​::​seq_cst 操作,并且 A 强烈地发生在 B 之前,那么 A 在 S 中先于 B。其次,对于对象 M 上的每一对原子操作 A 和 B,其中 A 是连贯有序的在B之前,S需要满足以下四个条件:

(4.1) 如果 A 和 B 都是 memory_order​::​seq_cst 操作,则 A 在 S 中先于 B;和

(4.2) 如果 A 是 memory_order​::​seq_cst 操作且 B 发生在 memory_order​::​seq_cst 围栏 Y 之前,则 A 在 S 中先于 Y;和

(4.3) 如果 memory_order​::​seq_cst 栅栏 X 发生在 A 之前且 B 是 memory_order​::​seq_cst 操作,则 X 在 S 中先于 B;和

(4.4) 如果 memory_order​::​seq_cst 栅栏 X 发生在 A 之前,B 发生在 memory_order​::​seq_cst 栅栏 Y 之前,则 X 在 S 中位于 Y 之前。

4

1 回答 1

10

为什么要引入“之前发生的强烈事件”?直观地说,它与“发生在之前”有什么区别和关系?

为“之前发生”做好准备!看看这个 cppref 的当前快照 https://en.cppreference.com/w/cpp/atomic/memory_order

在此处输入图像描述

似乎在 C++20 中添加了“之前发生”。

只是发生在之前

无论线程如何,如果以下任何一项为真,则评估 A 只会发生在评估 B 之前:

  1. A 在 B 之前排序

  2. A 与 B 同步

  3. A 简单地发生在 X 之前,而 X 简单地发生在 B 之前

注意:没有消费操作,简单的happens-before和happens-before关系是相同的。

所以 Simply-HB 和 HB 是相同的,除了它们如何处理消费操作。见 HB

发生之前

无论线程如何,如果以下任何一项为真,则评估 A 发生在评估 B 之前:

  1. A 在 B 之前排序

  2. 线程间发生在 B 之前

需要实现以确保发生之前的关系是非循环的,必要时通过引入额外的同步(只有在涉及消费操作时才需要,参见 Batty 等人)

他们在消费方面有何不同?请参阅线程间 HB

线程间发生之前

在线程之间,如果以下任何一项为真,则评估 A 线程间发生在评估 B 之前

  1. A 与 B 同步

  2. A 在 B 之前是依赖排序的

  3. ...

...

依赖顺序的操作(即使用释放/使用)是 HB,但不一定是 Simply-HB。

消费比获取更轻松,所以如果我理解正确的话,HB 比 Simply-HB 更轻松。

强烈发生之前

无论线程如何,如果以下任何一项为真,则评估 A 强烈发生在评估 B 之前:

  1. A 在 B 之前排序

  2. A 与 B 同步,A 和 B 都是顺序一致的原子操作

  3. A 在 X 之前排序,X 只是在 Y 之前发生,Y 在 B 之前排序

  4. A 强烈发生在 X 之前,并且 X 强烈发生在 B 之前

注意:非正式地,如果 A 强烈地发生在 B 之前,那么在所有情况下 A 似乎在 B 之前被评估。

注意:强烈发生之前不包括消费操作。

所以释放/消费操作不能是Strongly-HB。

Release/acquire 可以是 HB 和 Simply-HB(因为 release/acquire 同步)但不一定是 Strongly-HB。因为 Strongly-HB 明确表示 A 必须与 B 同步并且是顺序一致的操作。

发生之前有保证吗?

乙肝 简单-HB 强-HB
轻松
释放/消费 是的
释放/获取 是的 是的
SC 是的 是的 是的

注释中的“A 似乎在所有情况下都在 B 之前被评估”是什么意思?

所有上下文:所有线程/所有 CPU 看到(或“最终会同意”)相同的顺序。这是顺序一致性的保证——所有变量的全局总修改顺序。获取/释放链仅保证参与链的线程的感知修改顺序。理论上允许链外的线程看到不同的顺序。

我不知道为什么要引入 Strongly-HB 和 Simply-HB。也许有助于澄清如何围绕消费进行操作?Strongly-HB 有一个很好的特性——如果一个线程在 B 之前强烈地观察到 A,它知道所有线程都会观察到相同的事情。

消费历史:

Paul E. McKenney 负责使用 C 和 C++ 标准。消费保证指针分配和它指向的内存之间的顺序。它是因为 DEC Alpha 而发明的。DEC Alpha 可以推测性地取消引用指针,因此它也有一个内存栅栏来防止这种情况。DEC Alpha 不再生产,今天没有处理器具有这种行为。消费的目的是很放松。

于 2019-11-22T12:17:13.790 回答