同一线程上的两个连续 memory_order_release 存储可以相互重新排序吗?

Can two consecutive memory_order_release stores on the same thread be reordered with each other?

同一线程上的两个连续 memory_order_release 存储可以相互重新排序吗?是从同一个线程的角度还是从不同的线程加载它们?

documentation on CPP reference 说:

A store operation with this memory order performs the release operation: no reads or writes in the current thread can be reordered after this store.

所以在这个例子中:

std::atomic<uint64_t> a;
std::atomic<uint64_t> b;

// ...

a.store(0xDEADBEFF, std::memory_order::memory_order_release);
b.store(0xBEEFDEAD, std::memory_order::memory_order_release);

我预计 a 商店不能在b 商店 之后重新订购。但是,也许 b 商店仍然可以在 之前 a 商店重新排序,这是否等效?我不确定如何阅读该语言。

换句话说:文档似乎说 a 商店不能下移。是不是也保证b不能上移?

我正在尝试确定是否在另一个线程上我获取 b 并查看 0xBEEFDEAD 然后获取 a 如果我保证看到 a0xDEADBEEF.

// T1
a.store(0xDEADBEFF, std::memory_order::relaxed); // #1
b.store(0xBEEFDEAD, std::memory_order::release); // #2

// T2
if (b.load(std::memory_order::acquire) == 0xBEEFDEAD) {      // #3
   assert(a.load(std::memory_order::relaxed) == 0xDEADBEEF); // #4
}

1 排在 2 之前。2 与 3 同步,3 排在 4 之前。这意味着 1 发生在 4 之前。通过 [intro.races]p18,假设没有对 a, 4 的值必须取自 1,即断言永远不会触发。

重新排序内存操作(如读取和写入)的概念通常用于使线程间内存可见性问题变得更多"concrete",因为重新排序任务对于任何拥有阻塞和未阻塞的事情。但它不是线程间通信和内存可见性的基础。顺便说一下,memory_order_x 值是关于可见性的,而不是 "order"。不要使用术语 "memory order"!

释放语义由对任何可以看到存储值的线程的承诺定义。 (这就是为什么释放只是共享变量修改的 属性;读取原子对象,即使具有 memory_order_seq_cst 内存可见性,也永远不可能是释放操作。)

看到释放操作的写入值的线程可以假定先前的操作是 "finished"。这些对共享对象的操作必须是 "finished" 是读取和写入以及其他类似对象构造的操作(您的来源忘记提及)。已经完成的操作"before"(之前按程序执行顺序,甚至在不同的线程中,以相同的"finished"传递属性)可以被视为由执行读取的线程完成获取书面价值。 (如果您进行了轻松的阅读,则可以在之后使用获取屏障来获取获取阅读语义。)

需要注意的是,release和acquire操作是有界限的,决定了操作的互斥性,就像互斥锁一样:原子对象用于获取写线程和读线程之间的互斥。

a.store(0xDEADBEFF, std::memory_order::memory_order_release);

a 的存储不必具有任何特定的可见性,因为没有先前的内存操作(假设我们处于并行性的开始)使其可见。

b.store(0xBEEFDEAD, std::memory_order::memory_order_release);

一个释放操作(在 b 上)很重要:编译器不能 "reorder" 东西的原因是因为其他线程可以读取 b(这不是一个线程私有变量)并且可以看到特定值 0xBEEFDEAD 并可能得出释放发生的结论,并使用获取语义来 保证互斥 of:

  • 商店发布前的东西
  • 加载后的东西获取

也就是说,仅当用户代码检查该值是否已写入,并且该值可能来自那里时。所以基本上用户代码实现了互斥协议,但最终编译器让它工作。


关于引用:

The documentation on CPP reference says:

A store operation with this memory order performs the release operation: no reads or writes in the current thread can be reordered after this store.

我可以很容易地给出至少三种允许重新排序的情况。

第一个也是最明显的一个是编译器总是通过函数调用完成的重新排序:修改无法从其他任何地方访问的纯局部变量和外部调用。显然,这甚至无法通过屏障等特定调用来预防,因为它是一般转换。

其他的是无法通过外部函数调用进行的转换,但编译器知道原子操作,这与调用单独编译的函数不同:

  • 严格函数本地线程通信原语的任何操作,无论是互斥锁还是原子变量,都可以用任何东西重新排序,因为没有其他线程可以观察或与变量交互;
  • 当以编译器可以看到其上的所有操作的方式操作原子对象 A 时,如果存储的值从未更改(它保留其原始值),则可以重新排序对另一个对象的任何操作A.
  • 上发布商店的示例

这些可能是非常无趣和愚蠢的(谁将互斥锁用作局部变量?)特例,但它们在逻辑上是存在的。