标准C++ 11是否保证memory_order_seq_cst阻止StoreLoad在原子周围重新排序非原子?

Ale*_*lex 13 c++ concurrency standards multithreading c++11

标准C++ 11是否保证memory_order_seq_cst阻止StoreLoad重新排序原子操作以进行非原子内存访问?

众所周知,std::memory_orderC++ 11中有6 个,它指定了如何围绕原子操作对常规的非原子内存访问进行排序 - 工作草案,编程语言C++标准2016-07-12:http:/ /www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4606.pdf

§29.3顺序和一致性

§29.3/ 1

枚举memory_order指定1.10中定义的详细常规(非原子)内存同步顺序,并且可以提供操作排序.其枚举值及其含义如下:

众所周知,这6个memory_orders会阻止其中一些重新排序:

在此输入图像描述

但是,是否会memory_order_seq_cst阻止StoreLoad围绕原子操作重新排序以进行常规的非原子内存访问,或仅针对其他具有相同原子的原子进行重新排序memory_order_seq_cst

即,如果我们同时使用std::memory_order_seq_cstSTORE和LOAD,或仅用于其中一个,则阻止此StoreLoad重新排序?

std::atomic<int> a, b;
b.store(1, std::memory_order_seq_cst); // Sequential Consistency
a.load(std::memory_order_seq_cst); // Sequential Consistency
Run Code Online (Sandbox Code Playgroud)

关于Acquire-Release语义是明确的,它完全指定了跨原子操作的非原子内存访问重新排序:http://en.cppreference.com/w/cpp/atomic/memory_order


为防止StoreLoad重新排序,我们应该使用std::memory_order_seq_cst.

两个例子:

  1. std::memory_order_seq_cst对于STORE和LOAD:MFENCE

StoreLoad无法重新排序 - GCC 6.1.0 x86_64:https://godbolt.org/g/mVZJs0

std::atomic<int> a, b;
b.store(1, std::memory_order_seq_cst); // can't be executed after LOAD
a.load(std::memory_order_seq_cst); // can't be executed before STORE
Run Code Online (Sandbox Code Playgroud)
  1. std::memory_order_seq_cst仅适用于LOAD:没有MFENCE

StoreLoad可以重新排序 - GCC 6.1.0 x86_64:https://godbolt.org/g/2NLy12

std::atomic<int> a, b;
b.store(1, std::memory_order_release); // can be executed after LOAD
a.load(std::memory_order_seq_cst); // can be executed before STORE
Run Code Online (Sandbox Code Playgroud)

此外,如果C/C++ - 编译器使用C/C++ 11的替代映射到x86,它在LOAD之前刷新存储缓冲区:MFENCE,MOV (from memory)所以我们也必须使用std::memory_order_seq_cstLOAD:http://www.cl.cam.ac. uk /~pes20/cpp/cpp0xmappings.html正如在另一个问题中讨论这个例子作为方法(3):在处理器x86/x86_64中它是否有意义指令LFENCE?

即我们应该使用std::memory_order_seq_cstSTORE和LOAD来生成MFENCE保证,这会阻止StoreLoad重新排序.

memory_order_seq_cst对于原子加载或存储是否正确:

  • specifici Acquire-Release语义 - 阻止:LoadLoad,LoadStore,StoreStore重新排序原子操作以进行常规的非原子内存访问,

  • 但是阻止StoreLoad 只针对其他原子操作重新排序原子操作memory_order_seq_cst吗?

Ale*_*lex 7

没有,标准C ++ 11并不保证memory_order_seq_cst防止StoreLoad重新排序的non-atomic周围atomic(seq_cst)

即使是标准的C ++ 11并不保证memory_order_seq_cst防止StoreLoad的重新排序atomic(non-seq_cst)围绕atomic(seq_cst)

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

  • 所有memory_order_seq_cst操作都应有一个总顺序 S - C++11 标准:

§ 29.3

3

所有 memory_order_seq_cst 操作都应该有一个总顺序 S,与所有受影响位置的“发生在之前”顺序和修改顺序一致,这样从原子对象 M 加载值的每个 memory_order_seq_cst 操作 B 观察以下值之一: ...

  • 但是,任何具有弱于排序的原子操作memory_order_seq_cst都没有顺序一致性并且没有单一的总顺序,即非memory_order_seq_cst操作可以通过memory_order_seq_cst允许方向的操作重新排序- C++11 标准:

§ 29.3

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


C++ 编译器也允许这样的重新排序:

  1. 在 x86_64 上

通常 - 如果在编译器中 seq_cst 在存储后实现为屏障,则:

STORE-C(relaxed); LOAD-B(seq_cst); 可以重新排序 LOAD-B(seq_cst); STORE-C(relaxed);

GCC 7.0 x86_64 生成的 Asm 截图:https : //godbolt.org/g/4yyeby

此外,理论上可能 - 如果在编译器中 seq_cst 在加载前实现为屏障,则:

STORE-A(seq_cst); LOAD-C(acq_rel); 可以重新排序 LOAD-C(acq_rel); STORE-A(seq_cst);

  1. 在 PowerPC 上

STORE-A(seq_cst); LOAD-C(relaxed); 可以重新排序 LOAD-C(relaxed); STORE-A(seq_cst);

在 PowerPC 上也可以这样重新排序:

STORE-A(seq_cst); STORE-C(relaxed); 可以重新排序 STORE-C(relaxed); STORE-A(seq_cst);

如果甚至允许原子变量跨原子(seq_cst)重新排序,那么非原子变量也可以跨原子(seq_cst)重新排序。

GCC 4.8 PowerPC 生成的 Asm 截图:https : //godbolt.org/g/BTQBr8


更多细节:

  1. 在 x86_64 上

STORE-C(release); LOAD-B(seq_cst); 可以重新排序 LOAD-B(seq_cst); STORE-C(release);

英特尔® 64 和 IA-32 架构

8.2.3.4 加载可能会与较早的存储重新排序到不同的位置

即 x86_64 代码:

STORE-A(seq_cst);
STORE-C(release); 
LOAD-B(seq_cst);
Run Code Online (Sandbox Code Playgroud)

可以重新排序为:

STORE-A(seq_cst);
LOAD-B(seq_cst);
STORE-C(release); 
Run Code Online (Sandbox Code Playgroud)

这可能发生,因为之间c.storeb.load不是mfence

x86_64 - GCC 7.0https : //godbolt.org/g/dRGTaO

C++ & asm - 代码:

#include <atomic>

// Atomic load-store
void test() {
    std::atomic<int> a, b, c;
    a.store(2, std::memory_order_seq_cst);          // movl 2,[a]; mfence;
    c.store(4, std::memory_order_release);          // movl 4,[c];
    int tmp = b.load(std::memory_order_seq_cst);    // movl [b],[tmp];
}
Run Code Online (Sandbox Code Playgroud)

它可以重新排序为:

#include <atomic>

// Atomic load-store
void test() {
    std::atomic<int> a, b, c;
    a.store(2, std::memory_order_seq_cst);          // movl 2,[a]; mfence;
    int tmp = b.load(std::memory_order_seq_cst);    // movl [b],[tmp];
    c.store(4, std::memory_order_release);          // movl 4,[c];
}
Run Code Online (Sandbox Code Playgroud)

此外,x86/x86_64 中的顺序一致性可以通过四种方式实现:http : //www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html

  1. LOAD(无围栏)和STORE+MFENCE
  2. LOAD (无围栏)和 LOCK XCHG
  3. MFENCE+LOADSTORE(无围栏)
  4. LOCK XADD( 0 ) 和STORE(无围栏)
  • 1 和 2 种方式:LOAD和 ( STORE+ MFENCE)/( LOCK XCHG) - 我们在上面回顾过
  • 3 和 4 种方式:( MFENCE+ LOAD)/LOCK XADDSTORE- 允许下一次重新排序:

STORE-A(seq_cst); LOAD-C(acq_rel); 可以重新排序 LOAD-C(acq_rel); STORE-A(seq_cst);


  1. 在 PowerPC 上

STORE-A(seq_cst); LOAD-C(relaxed); 可以重新排序 LOAD-C(relaxed); STORE-A(seq_cst);

允许存储加载重新排序(表 5 - PowerPC):http : //www.rdrop.com/users/paulmck/scalability/paper/whymb.2010.06.07c.pdf

加载后重新排序的商店

即 PowerPC 代码:

STORE-A(seq_cst);
STORE-C(relaxed); 
LOAD-C(relaxed); 
LOAD-B(seq_cst);
Run Code Online (Sandbox Code Playgroud)

可以重新排序为:

LOAD-C(relaxed);
STORE-A(seq_cst);
STORE-C(relaxed); 
LOAD-B(seq_cst);
Run Code Online (Sandbox Code Playgroud)

PowerPC - GCC 4.8https : //godbolt.org/g/xowFD3

C++ & asm - 代码:

#include <atomic>

// Atomic load-store
void test() {
    std::atomic<int> a, b, c;       // addr: 20, 24, 28
    a.store(2, std::memory_order_seq_cst);          // li r9<-2; sync; stw r9->[a];
    c.store(4, std::memory_order_relaxed);          // li r9<-4; stw r9->[c];
    c.load(std::memory_order_relaxed);              // lwz r9<-[c];
    int tmp = b.load(std::memory_order_seq_cst);    // sync; lwz r9<-[b]; ... isync;
}
Run Code Online (Sandbox Code Playgroud)

通过a.store分为两部分 - 它可以重新排序为:

#include <atomic>

// Atomic load-store
void test() {
    std::atomic<int> a, b, c;       // addr: 20, 24, 28
    //a.store(2, std::memory_order_seq_cst);            // part-1: li r9<-2; sync;
    c.load(std::memory_order_relaxed);              // lwz r9<-[c];
    a.store(2, std::memory_order_seq_cst);          // part-2: stw r9->[a];
    c.store(4, std::memory_order_relaxed);          // li r9<-4; stw r9->[c];
    int tmp = b.load(std::memory_order_seq_cst);    // sync; lwz r9<-[b]; ... isync;
}
Run Code Online (Sandbox Code Playgroud)

其中 load-from-memorylwz r9<-[c];执行早于 store-to-memory stw r9->[a];


在 PowerPC 上也可以这样重新排序:

STORE-A(seq_cst); STORE-C(relaxed); 可以重新排序 STORE-C(relaxed); STORE-A(seq_cst);

因为 PowerPC 具有弱内存排序模型 - 允许 Store-Store 重新排序(表 5 - PowerPC):http : //www.rdrop.com/users/paulmck/scalability/paper/whymb.2010.06.07c.pdf

商店后重新排序的商店

即在 PowerPC 上操作 Store 可以与其他 Store 重新排序,然后可以重新排序前面的示例,例如:

#include <atomic>

// Atomic load-store
void test() {
    std::atomic<int> a, b, c;       // addr: 20, 24, 28
    //a.store(2, std::memory_order_seq_cst);            // part-1: li r9<-2; sync;
    c.load(std::memory_order_relaxed);              // lwz r9<-[c];
    c.store(4, std::memory_order_relaxed);          // li r9<-4; stw r9->[c];
    a.store(2, std::memory_order_seq_cst);          // part-2: stw r9->[a];
    int tmp = b.load(std::memory_order_seq_cst);    // sync; lwz r9<-[b]; ... isync;
}
Run Code Online (Sandbox Code Playgroud)

其中 store-to-memorystw r9->[c];早于 store-to-memory 执行stw r9->[a];