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

共4个回答, 标签: c++ multithreading c++11 language-lawyer as-if

的文件编制std::memory_order关于 cppreference.com有一个宽松订购的例子:

轻松订购

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

例如,x 和 y 最初为零,

//线程 1:
R1 = y.load (std:: memory_order_relaxed);//A
X.store (r1,std:: memory_order_relaxed);//B
//线程 2:
R2 = x.load (std:: memory_order_relaxed);//C
Y.store (42,std:: memory_order_relaxed);//D

允许产生 r1 = = r2 = = 42,因为,尽管 A 是排序前线程 1 内的 B 和 C 是_排序前_D 在线程 2 内,没有什么能阻止 D 以 y 的修改顺序出现在 A 之前,而 B 以 x 的修改顺序出现在 C 之前。D 对 y 的副作用对线程 1 中的负载 A 可见,而 B 对 x 的副作用对线程 2 中的负载 C 可见。特别是,如果 D 在线程 2 的 C 之前完成,这可能会发生,由于编译器重新排序或在运行时。

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

根据 sequened-before 的定义,可以在评价顺序,如果 A 在 B 之前被排序,那么 A 的评估将在 B 的评估开始之前完成。因为 C 在线程 2 中是在 D 之前排序的,所以 C 必须在 D 开始之前完成,因此快照最后一句话的条件部分永远不会被满足。

第1个答案
    I believe cppreference is right. I think this boils down to the "as-if" rule [intro.execution]/1. Compilers are only bound to reproduce the observable behavior of the program described by your code. A sequenced-before relation is only established between evaluations from the perspective of the thread in which these evaluations are performed [intro.execution]/15. That means when two evaluations sequenced one after the other appear somewhere in some thread, the code actually running in that thread must behave as if whatever the first evaluation does did indeed affect whatever the second evaluation does. For example
Int x = 0;
X = 42;
Std:: cout <<x;

必须打印 42。然而,编译器实际上并不需要将值 42 存储到对象中x before reading the value back from that object to print it. It may as well remember that the last value to be stored in x was 42 and then simply print the value 42 directly before doing an actual store of the value 42 to x. In fact, if x is a local variable, it may as well just track what value that variable was last assigned at any point and never even create an object or actually store the value 42. There is no way for the thread to tell the difference. The behavior is always going to be as if there was a variable and as if the value 42 were actually stored in an object x之前从该对象加载。但这并不意味着生成的机器代码必须存储和加载任何地方。所需要的是,生成的机器代码的可观察行为与如果所有这些事情都实际发生时的行为是无法区分的。

如果我们看看

R2 = x.load (std:: memory_order_relaxed);//C
Y.store (42,std:: memory_order_relaxed);//D

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

第2个答案
    It is sometimes possible for an action to be ordered relative to two other sequences of actions, without implying any relative ordering of the actions in those sequences relative to each other.

例如,假设一个人有以下三个事件:

  • 存储 1 到 p1
  • 将 p2 装入 temp
  • 存储 2 到 p3

P2 的读取在 p1 写入之后和 p3 写入之前是独立排序的,但是 p1 和 p3 都参与的顺序没有特别。根据对 p2 所做的事情,对于编译器来说,将 p1 推迟到 p3 之后,仍然使用 p2 实现所需的语义可能是不切实际的。然而,假设编译器知道上述代码是一个更大的序列的一部分:

  • 存储 1 到 p2 [在 p2 加载之前排序]
  • 【做上面的】
  • 将 3 存储到 p1 [在另一个存储到 p1 之后排序]

在这种情况下,它可以确定在上述代码之后,它可以将存储重新排序为 p1,并将其与以下存储合并,从而导致代码写入 p3 而不首先写入 p1:

  • 将 temp 设置为 1
  • 存储温度到 p2
  • 存储 2 到 p3
  • 存储 3 到 p1

虽然看起来数据依赖会导致排序关系的某些部分表现为传递的,但是编译器可能会识别不存在明显数据依赖的情况, 因此不会产生人们所期望的传递效应。

第3个答案
    If there are two statements, the compiler will generate code in sequential order so code for the first one will be placed prior to the second one.  But cpus internally have pipelines and are able to run assembly operations in parallel.  Statement C is a load instruction.  While memory is being fetched the pipeline will process the next few instructions and given they are not dependent on the load instruction they could end up being executed prior to C being finished (e.g. data for D was in cache, C in main memory).

如果用户确实需要按顺序执行这两个语句,可以使用更严格的内存排序操作。一般来说,只要程序逻辑正确,用户就不在乎。

第4个答案
    Whatever you think is equally valid. The standard does not say what executes sequentially, what does not, and how it can be mixed up.

这取决于你和每一个程序员,在混乱的基础上建立一个一致的语义,这是一项值得多个博士学位的工作。

相关问题

如何在异步和线程中执行大量 sql 查询 如何在 Java 8 中创建一个阻塞的背景加载器? 在 cppreference 中,宽松排序的解释是错误的吗? 如何将模板参数从类型更改为非类型, 使 SFINAE 工作? 允许在易失对象上进行优化 访问 std:: 字符串中的元素, 其中字符串的位置大于其大小