在 C++ 中重新排序原子操作

reordering atomic operations in C++

假设我有 2 个线程:

int value = 0;
std::atomic<bool> ready = false;

thread 1:
value = 1
ready = true;

thread 2:
while (!ready);
std::cout << value;

这个程序能输出0吗?

我了解了 C++ 内存模型 - 特别是顺序一致性,我认为这是默认设置,但不是特别清楚。编译器是只需要将原子操作以相对于彼此的正确顺序放置,还是需要将原子操作以相对于所有其他操作的正确顺序放置?

根据 ShadowRanger 的回应,它就像一个内存屏障。 但是,有关它为什么这样做的更多详细信息,我建议查看 Herb Sutter's talk on atomic weapons。他详细介绍了原子的工作原理和原因。

默认情况下,对原子变量的操作是使用 memory_order_seq_cst 语义完成的,这保证不会进行重新排序。

因此行:value = 1 不能在原子赋值:value = 1 下面重新排序,因此行 std::cout << value; 将始终打印 1。

根据相同的规则,行:std::cout << value; 不能重新排序
在线上方:while (!ready);.

是的,但这只是因为您使用了默认值。

Cst 受到影响是因为它使用全局范围进行重新排序;这是旧架构的产物。较新的架构具有更细粒度的排序范围,因此您可能希望 标准更新 会在不久的将来使您的代码失效。

写入队列最终可能会包含数十个条目,每个条目的解析速度都非常慢。将这些划分为重要的部分和不重要的部分是一个明显的步骤,新架构已经包含了这一步骤。

C++ 标准创建委员会显然超出了他们的深度,应该停止发明无用的废话。

首先注意:绝对不可能在支持线程的任何版本的 C 和 C++ 中推理任何 C 或 C++ 程序,因为存在 UB(未定义行为)的可能性,因为没有明确定义的抽象线程语义,或任何定义的语义。这是 C 和 C++ 语义中的另一个主要理论和实践缺陷(在许多其他严重缺陷之上)。

但是您可以从实际的角度进行推理:编译器在线程原语的实现中是非常可预测的(将来可能不会是这种情况,因为编译器编写者会精通线程语义并开始使用 UB 的声明来破坏事物) .

使用线程通信原语时,编译器会做正确的事情来保证信息流。 while (!ready); 保证线程在 设置原子对象后退出循环 :有一个定义明确的 "past".

作为定义不明确 "past" 的真实世界示例,请记住阿波罗与宇航员在休斯敦上空交谈的音频交流:由于宇航员离得很远,所以没有明确定义谁先开始讲话的概念,我们唯一的录音(来自休斯顿)显示了一个命令,但来自宇宙飞船的假设录音会显示另一个,而且这两个命令都不正确。宇航员和休斯顿开始毫无秩序地交谈起来,其他人都没有过去。除非你看到这一点,否则你不能声称理解相对论。

使用多线程,您可以进行其他人过去不存在的内存操作,并且无法知道如果他们尝试使用在非过去。

Is this program able to output 0?

没有.

你的推理是正确的。在 ISO C++ seq_cst 和 acq_rel 中,原子可以在线程之间创建 happens-before / after 关系,使一个线程可以安全地写入一个非原子变量,然后另一个线程可以安全地读取它,而不会发生数据争用UB.

在这种情况下您已经正确地这样做了:自旋等待循环是来自标志的 seq-cst 加载,只有在看到 true 值时才会退出循环。非原子 value 的计算发生在看到 true 的加载之后。在编写器中,顺序发布存储确保标志存储在值存储之前不可见。


在为普通 ISA 编译为 asm 时,编译器必须尊重非原子存储在发布存储之前的顺序,以及非原子加载在获取加载之后的顺序。 除非它可以以某种方式证明对于观察到这一点的任何其他可能的线程仍然会有 UB。