“之前发生了什么”是什么意思?

cur*_*guy 8 c++ multithreading memory-model language-lawyer happens-before

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

Hum*_*ago 7

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

也要为“之前发生过”做好准备!查看 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 之前

注意:没有消费操作,简单的发生在和发生在之前的关系是一样的。

所以Simply-HB 和HB 是一样的,只是它们处理消费操作的方式不同。见HB

发生在之前

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

1) A 先于 B 排序

2) A 线程间发生在 B 之前

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

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

线程间发生在之前

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

1) A 与 B 同步

2) A 在 B 之前是依赖顺序的

3) ...

...

依赖顺序(即使用释放/消费)的操作是 HB,但不一定是简单的 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 同步并且是顺序一致的操作。

                            Is happens-before guaranteed?

                        HB             Simply-HB          Strongly-HB

relaxed                 no                 no                 no
release/consume        yes                 no                 no      
release/acquire        yes                yes                 no
S.C.                   yes                yes                yes
Run Code Online (Sandbox Code Playgroud)

注释中的“A 似乎在所有上下文中都先于 B 求值”是什么意思?

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

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

消费历史:

Paul E. McKenney 负责在 C 和 C++ 标准中使用。Consume 保证指针赋值和它指向的内存之间的顺序。它的发明是因为 DEC Alpha。DEC Alpha 可以推测性地取消引用一个指针,因此它也有一个内存栅栏来防止这种情况。DEC Alpha 不再制造,今天没有处理器有这种行为。消费旨在非常放松。

  • 我想说,consum 的存在是因为 *除了* Alpha 之外的所有弱有序 ISA。在 Alpha asm 中,唯一的选项是放宽和获取(和 seq-cst),而不是依赖排序。“mo_consume”旨在利用真实 CPU 上的数据依赖性排序,并形式化编译器无法通过分支预测打破数据依赖性。例如 `int *p = load();` `tmp = *p;` 可能会被编译器引入 `if(p==known_address) tmp = *known_address; 所破坏。else tmp=*p;` 如果有某种理由期望某个指针值是通用的。这对于放松来说是合法的,但不是消费。 (3认同)
  • 好悲伤。我几乎后悔问了这个问题。我想回去解决*简单的* C++ 问题,例如迭代器失效规则、参数相关名称查找、模板用户定义的转换运算符、模板参数推导、名称查找在模板成员中的基类中查找时,以及当您可以在对象构造开始时转换为虚拟基础时。 (2认同)