atomic_thread_fence(memory_order_seq_cst)是否具有完整内存屏障的语义?

Eri*_*c Z 7 c++ c++11

完整/通用内存屏障是在屏障之前指定的所有LOAD和STORE操作看起来发生在屏障之后相对于系统的其他组件指定的所有LOAD和STORE操作之前的屏障.

根据cppreference,memory_order_seq_cst等于memory_order_acq_rel在所有标记的操作上加上单个总修改顺序.但据我所知,C++ 11中的获取和释放栏都没有强制执行#StoreLoad(加载后存储)排序.发布围栏要求以后的任何写入都不能重新排序先前的读/写; 获取围栏要求不能对任何先前的读取重新排序后续读/写.如果我错了,请纠正我;)

举个例子,

atomic<int> x;
atomic<int> y;

y.store(1, memory_order_relaxed);            //(1)
atomic_thread_fence(memory_order_seq_cst);   //(2)
x.load(memory_order_relaxed);                //(3)
Run Code Online (Sandbox Code Playgroud)

是否允许优化编译器将指令(3)重新排序到之前(1),以使其有效看起来像:

x.load(memory_order_relaxed);                //(3)
y.store(1, memory_order_relaxed);            //(1)
atomic_thread_fence(memory_order_seq_cst);   //(2)
Run Code Online (Sandbox Code Playgroud)

如果这是一个有效的转换,那么它证明atomic_thread_fence(memory_order_seq_cst)并不一定包含完整障碍所具有的语义.

Ale*_*lex 7

atomic_thread_fence(memory_order_seq_cst) 总是产生一个完整的屏障。

  • x86_64: MFENCE
  • 电源PC: hwsync
  • 伊塔努姆: mf
  • ARMv7 / ARMv8: dmb ish
  • MIPS64: sync

最重要的是:观察线程可以简单地以不同的顺序观察,并且与您在被观察线程中使用的栅栏无关。

优化编译器是否允许将指令(3)重新排序到(1)之前?

不,这是不允许的。但是在多线程程序的全局可见性中,这是正确的,仅在以下情况下:

  • 其他线程使用相同的值memory_order_seq_cst进行原子读/写操作
  • 或者如果其他线程atomic_thread_fence(memory_order_seq_cst);也在 load() 和 store() 之间使用相同的- 但这种方法一般不能保证顺序一致性,因为顺序一致性是更有力的保证

工作草案,C++ 编程语言标准 2016-07-12:http : //www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4606.pdf

§ 29.3 顺序和一致性

§ 29.3 / 8

[ 注意:memory_order_seq_cst 仅确保没有数据竞争且仅使用 memory_order_seq_cst 操作的程序的顺序一致性。除非格外小心,否则任何较弱排序的使用都会使此保证无效。特别是,memory_order_seq_cst 栅栏确保仅栅栏本身的总顺序。通常,栅栏不能用于恢复具有较弱排序规范的原子操作的顺序一致性。— 尾注 ]


它如何映射到汇编器:

情况1:

atomic<int> x, y

y.store(1, memory_order_relaxed);            //(1)
atomic_thread_fence(memory_order_seq_cst);   //(2)
x.load(memory_order_relaxed);                //(3)
Run Code Online (Sandbox Code Playgroud)

此代码并不总是等同于 Case-2 的含义,但此代码在 STORE 和 LOAD 之间产生相同的指令,以及如果 LOAD 和 STORE 都使用memory_order_seq_cst- 这是顺序一致性,可防止 StoreLoad 重新排序,Case-2

atomic<int> x, y;

y.store(1, memory_order_seq_cst);            //(1)

x.load(memory_order_seq_cst);                //(3)
Run Code Online (Sandbox Code Playgroud)

附上一些注意事项:

  1. 它可能会添加重复的指令(如下面的 MIPS64 示例)
  2. 或者可以使用其他指令形式的类似操作:

    • 与 x86_64 的替代 3/4 映射LOCK一样MFENCE,-prefix完全刷新 Store-Buffer以防止 StoreLoad 重新排序
    • 或 ARMv8 - 我们知道,这DMB ISH是阻止 StoreLoad 重新排序的完全屏障:http ://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.den0024a/CHDGACJD.html

ARMv8-A 指南

表 13.1。屏障参数

ISH 任何 - 任何

Any - Any 这意味着加载和存储都必须在屏障之前完成。在程序顺序中出现在屏障之后的加载和存储都必须等待屏障完成。

防止两条指令重新排序可以通过这两条指令之间的附加指令来完成。正如我们所看到的,第一个 STORE(seq_cst) 和下一个 LOAD(seq_cst)之间生成的指令与 FENCE(seq_cst) ( atomic_thread_fence(memory_order_seq_cst))

C/C++11memory_order_seq_cst到不同 CPU 架构的映射:load(), store(), atomic_thread_fence():

注意atomic_thread_fence(memory_order_seq_cst); 总是生成 Full-barrier:

  • x86_64的: STORE- MOV (into memory),MFENCE,LOAD- MOV (from memory),fence-MFENCE

  • x86_64的-ALT:STORE- MOV (into memory),LOAD- ,fence-MFENCE,MOV (from memory)MFENCE

  • x86_64的-ALT3:STORE- (LOCK) XCHG,LOAD- MOV (from memory),fence- MFENCE-满垒

  • x86_64的-ALT4:STORE- MOV (into memory),LOAD- LOCK XADD(0),fence- MFENCE-满垒

  • PowerPC的: STORE- hwsync; st,LOAD- ,fence-hwsync;ld; cmp; bc; isynchwsync

  • 安腾:STORE- st.rel;mf,LOAD- ld.acq,fence-mf

  • 的ARMv7: STORE- dmb ish; str;dmb ish,LOAD- ldr; dmb ish,fence-dmb ish

  • 的ARMv7-ALT:STORE- dmb ish; str,LOAD- ,fence-dmb ish;ldr; dmb ishdmb ish

  • ARMv8(AArch32):STORE- STL,LOAD- LDA,fence- DMB ISH-满垒

  • ARMv8(AArch64): STORE- STLR,LOAD- LDAR,fence- DMB ISH-满垒

  • MIPS64: STORE- sync; sw;sync;,LOAD- sync; lw; sync;,fence-sync

描述了 C/C++11 语义到不同 CPU 架构的所有映射:load()、store()、atomic_thread_fence():http ://www.cl.cam.ac.uk/~pes20/cpp/ cpp0xmappings.html

因为 Sequential-Consistency 阻止了 StoreLoad 重新排序,并且因为 Sequential-Consistency(store(memory_order_seq_cst)和 next load(memory_order_seq_cst))在其之间生成与 相同的指令atomic_thread_fence(memory_order_seq_cst),所以atomic_thread_fence(memory_order_seq_cst)阻止了 StoreLoad 重新排序。


Ant*_*ams 0

C++ 栅栏并不直接等同于 CPU 栅栏指令,尽管它们很可能是这样实现的。C++ 栅栏是 C++ 内存模型的一部分,它与可见性和排序约束有关。

鉴于处理器通常会在将值提供给其他核心或处理器之前对读取和写入以及本地缓存值进行重新排序,因此其他处理器可见效果的顺序通常是不可预测的。

因此,在考虑这些语义时,重要的是要考虑您要阻止的是什么。

假设代码按照编写方式映射到机器指令,(1) 然后 (2) 然后 (3),并且这些指令保证 (1) 在执行 (3) 之前全局可见。

该代码片段的全部目的是与另一个线程进行通信。您无法保证当此代码片段在我们的处理器上执行时,另一个线程正在任何处理器上运行。因此,整个代码片段可以不间断地运行,并且(3)仍然会读取x执行(1)时的任何值。在这种情况下,它与(3)(1)(2)的执行顺序没有区别。

所以:是的,这是一个允许的优化,因为你无法区分。