我是否正确理解 std::memory_order 的语义?

Do I understand the semantics of std::memory_order correctly?

c++reference.com 表示 memory_order::seq_cst:

A load operation with this memory order performs an acquire operation, a store performs a release operation, and read-modify-write performs both an acquire operation and a release operation, plus a single total order exists in which all threads observe all modifications in the same order.

[ Q1 ]: 这是否意味着订单直接向下通过 all (others + this) atomic_vars with memory_order::seq_cst 的每个操作?

[Q2]: 而release,acquire,rel_acq不属于“单笔总订单”?

我理解seq_cst相当于其他三个有写,读和write_read操作,但我很困惑seq_cst是否可以命令其他atomic_vars 也一样,不仅是同一个var.

cppreference 只是对 C++ 标准的总结,有时它的文字不够精确。实际标准草案明确表示:atomics.order 中的 final C++20 working draft N4681 状态,par。 4(第 1525 页):

There is a single total order S on all memory_order::seq_cst operations, including fences, that satisfies the following constraints [...]

这清楚地说明了 所有 seq_cst 操作,而不仅仅是对特定对象的所有操作。

注释 6 和注释 7 进一步强调该顺序不适用于较弱的内存顺序:

6 [Note: We do not require that S be consistent with “happens before” (6.9.2.1). This allows more efficient implementation of memory_order::acquire and memory_order::release on some machine architectures. It can produce surprising results when these are mixed with memory_order::seq_cst accesses. — end note]

7 [Note: memory_order::seq_cst ensures sequential consistency only for a program that is free of data races and uses exclusively memory_order::seq_cst atomic operations. Any use of weaker ordering will invalidate this guarantee unless extreme care is used. In many cases, memory_order::seq_cst atomic operation

我觉得这部分不完整:

A load operation with this memory order performs an acquire operation, a store performs a release operation, and read-modify-write performs both an acquire operation and a release operation, plus a single total order exists in which all threads observe all modifications in the same order.

如果这些东西(释放存储、获取负载和总存储顺序)实际上足以提供顺序一致性,那将意味着释放和获取操作本身的顺序将比实际更严格。

我们来看下面的反例:

CPU1:
   a = 1 // release store
   int r1 = b // acquire load

然后根据上面对 SC 的定义(并且已知属性顺序一致性必须符合名称),我假设 a 的存储和 b 的加载可以未被重新排序:

  • 我们有一个发布存储和一个获取加载
  • 我们(可以)对所有 loads/stores
  • 进行总排序

所以我们满足了上述顺序一致性的定义。

但是可以对发布存储后跟获取加载到不同地址进行重新排序。典型的例子是 Dekker 的算法。因此上面的 SC 定义被打破了,因为它缺少内存顺序需要保留程序顺序。除了编译器搞砸之外,这种违规的典型原因是大多数现代 CPU 所拥有的存储缓冲区可能会导致旧存储被重新排序为不同地址的新负载。

单个总订单与 CPU 本地指令重新排序不同,您可以通过例如存储缓冲区。它实际上意味着在某个时刻,某个操作会在内存顺序中生效,没有人应该不同意这一点。对此的标准试金石是独立写入的独立读取 (IRIW):

CPU1:
   A=1
CPU2:
   B=1
CPU3:
   r1=A
   [LoadLoad]
   r2=B
CPU4:
   r3=B
   [LoadLoad]
   r4=A

所以 CPU3 和 CPU4 会不会看到商店以不同的顺序到达不同的地址?如果答案是肯定的,那么 load/stores 上的总订单不存在。

loads/stores 没有总订单的另一个原因是存储到负载转发 (STLF)。

CPU1:
   A=1
   r1=A
   r2=B

CPU2:
   B=1
   r3=B
   r4=A

r1=1,r2=0,r3=1,r4=0有可能吗?

在 X86 上,这是可能的,因为存储到负载转发。因此,如果 CPU1 存储 A,然后加载 A,则 CPU 必须在存储缓冲区中查找 A 的值。这导致 A 的存储不是原子的;本地 CPU 可以提前看到商店,结果是 loads/stores 没有总订单存在。

因此,它不是对所有 load/stores 进行总排序,而是对存储进行总排序,这就是 X86 为其内存模型(总存储排序)命名的方式。

[编辑] 做了一些说明。我清理了一些文本并清理了原始示例,因为它具有误导性。