cppreference 中对宽松排序的解释是错误的吗?

abi*_*ile 13 c++ multithreading language-lawyer c++11 as-if

在cppreference.com 上的文档中std::memory_order,有一个轻松排序的示例:

轻松下单

标记的原子操作memory_order_relaxed不是同步操作;它们不会在并发内存访问之间强加顺序。它们只保证原子性和修改顺序的一致性。

例如,x 和 y 最初为零,

// Thread 1:
r1 = y.load(std::memory_order_relaxed); // A
x.store(r1, std::memory_order_relaxed); // B
// Thread 2:
r2 = x.load(std::memory_order_relaxed); // C
y.store(42, std::memory_order_relaxed); // D
Run Code Online (Sandbox Code Playgroud)

允许产生 r1 == r2 == 42 因为,虽然 A在线程 1 中先于 B排序, C 在线程 2 中在D之前排序,但没有什么能阻止 D 在 y 的修改顺序中出现在 A 之前,而 B 从在 x 的修改顺序中出现在 C 之前。线程 1 中的负载 A 可以看到 D 对 y 的副作用,而线程 2 中的负载 C 可以看到 B 对 x 的副作用。特别是,如果 D 在 C 之前完成,则可能发生这种情况线程 2,由于编译器重新排序或在运行时。

它说“C 在线程 2 中排在 D 之前”。

根据sequenced-before 的定义,可以在Order of evaluation 中找到,如果A 在B 之前被排序,那么A 的评估将在B 的评估开始之前完成。由于线程 2 中 C 在 D 之前被排序,因此 C 必须在 D 开始之前完成,因此快照最后一句的条件部分将永远不会满足。

Mic*_*zel 13

我相信 cppreference 是正确的。我认为这归结为“as-if”规则[intro.execution]/1。编译器只能重现由您的代码描述的程序的可观察行为。甲测序-之前仅评估之间从其中这些评价则执行线程的透视建立关系[intro.execution] / 15。这意味着当两个连续的求值出现在某个线程的某处时,实际在该线程中运行的代码必须表现得好像第一个求值所做的任何事情确实影响了第二个求值所做的任何事情。例如

int x = 0;
x = 42;
std::cout << x;
Run Code Online (Sandbox Code Playgroud)

必须打印 42。但是,在x从该对象读回值以打印它之前,编译器实际上不必将值 42 存储到一个对象中。还可以记住要存储的最后一个值x是 42 ,然后在实际将值 42 存储到 之前直接打印值 42 x。事实上,如果x是一个局部变量,它也可能只是跟踪该变量在任何时候最后分配的值,甚至从不创建对象或实际存储值 42。线程无法区分。的行为总是要仿佛存在一个变量,仿佛值42实际上存储在对象x 之前正在从该对象加载。但这并不意味着生成的机器代码必须在任何地方实际存储和加载任何内容。所需要的只是生成的机器代码的可观察行为与如果所有这些事情真的发生时的行为是无法区分的。

如果我们看

r2 = x.load(std::memory_order_relaxed); // C
y.store(42, std::memory_order_relaxed); // D
Run Code Online (Sandbox Code Playgroud)

那么是的,C 在 D 之前被排序。但是当孤立地从这个线程来看时,C 所做的任何事情都不会影响 D 的结果。而且 D 所做的任何事情都不会改变 C 的结果。一个可以影响另一个的唯一方法是作为另一个线程中发生的事情的间接结果。但是,通过指定std::memory_order_relaxed,您明确说明另一个线程观察加载和存储的顺序是无关紧要的。由于没有其他线程可以按任何特定顺序观察加载和存储,因此其他线程无法使 C 和 D 以一致的方式相互影响。因此,实际执行加载和存储的顺序无关紧要。因此,编译器可以自由地对它们重新排序。而且,正如在该示例下面的解释中所提到的,如果在从 C 加载之前执行了来自 D 的存储,那么 r1 == r2 == 42 确实可以发生......

  • @curiousguy这个说法似乎类似于[你的其他,以前的C++福音](/sf/ask/4175913801/ -同时#comment105473406_59655911)。 (5认同)
  • @Fuleeish No. C 必须在 D 之前发生,就它们发生的线程而言。从另一个背景观察可能与该观点不一致。 (4认同)
  • @curiousguy 标准**确实**在脚注中将其中一项规定标记为“假设规则”:“此规定有时称为“假设”规则”[intro.execution](https://timsong -cpp.github.io/cppwp/n3337/intro.execution#footnote-5) (2认同)