std :: atomic :: load的内存排序行为

Alf*_*Alf 5 c++ atomic memory-fences atomicity c++11

我错误地认为atomic :: load也应该充当内存屏障,确保所有先前的非原子写入将被其他线程看到?

为了显示:

volatile bool arm1 = false;
std::atomic_bool arm2 = false;
bool triggered = false;
Run Code Online (Sandbox Code Playgroud)

线程1:

arm1 = true;
//std::std::atomic_thread_fence(std::memory_order_seq_cst); // this would do the trick 
if (arm2.load())
    triggered = true;
Run Code Online (Sandbox Code Playgroud)

线程2:

arm2.store(true);
if (arm1)
    triggered = true;
Run Code Online (Sandbox Code Playgroud)

我预计在执行两个'触发'之后都是真的.请不要建议使arm1原子,重点是探索atomic :: load的行为.

虽然我不得不承认我并不完全理解内存顺序的不同松弛语义的正式定义,但我认为顺序一致的顺序非常简单,因为它保证"存在单个总顺序,其中所有线程都观察到所有修改以相同的顺序." 对我来说,这意味着std :: atomic :: load与默认内存顺序std :: memory_order_seq_cst也将充当内存栅栏."顺序一致排序"下的声明进一步证实了这一点:

总顺序排序需要在所有多核系统上使用完整的内存栅栏CPU指令.

然而,我下面的简单示例演示了MSVC 2013,gcc 4.9(x86)和clang 3.5.1(x86)的情况并非如此,其中原子载荷只是转换为加载指令.

#include <atomic>

std::atomic_long al;

#ifdef _WIN32
__declspec(noinline)
#else
__attribute__((noinline))
#endif
long load() {
    return al.load(std::memory_order_seq_cst);
}

int main(int argc, char* argv[]) {
    long r = load();
}
Run Code Online (Sandbox Code Playgroud)

使用gcc,这看起来像:

load():
   mov  rax, QWORD PTR al[rip]   ; <--- plain load here, no fence or xchg
   ret
main:
   call load()
   xor  eax, eax
   ret
Run Code Online (Sandbox Code Playgroud)

我将省略基本相同的msvc和clang.现在在ARM的gcc上,我们得到了我的预期:

load():
     dmb    sy                         ; <---- data memory barrier here
     movw   r3, #:lower16:.LANCHOR0
     movt   r3, #:upper16:.LANCHOR0
     ldr    r0, [r3]                   
     dmb    sy                         ; <----- and here
     bx lr
main:
    push    {r3, lr}
    bl  load()
    movs    r0, #0
    pop {r3, pc}
Run Code Online (Sandbox Code Playgroud)

这不是一个学术问题,它会在我们的代码中导致一种微妙的竞争条件,这使我对std :: atomic行为的理解产生了疑问.

Myl*_*ock 3

叹气,这对于评论来说太长了:

原子的含义不是“对于系统的其余部分来说似乎是瞬时发生的”吗?

对于这一点,我会说“是”,也可能说“不是”,这取决于你如何看待它。对于用 写入SEQ_CST,是的。但至于如何处理原子加载,请查看 C++11 标准的 29.3。具体来说,29.3.3 确实很好读,而 29.3.4 可能正是您正在寻找的内容:

对于读取原子对象 M 的值的原子操作 B,如果在 B 之前有一个 memory_order_seq_cst 栅栏 X 排序,则 B 会观察总顺序 S 中 X 之前的 M 的最后一个 memory_order_seq_cst 修改,或者稍后对M 的修改顺序。

基本上,SEQ_CST就像标准所说的那样强制全局顺序,但读取可以返回旧值而不违反“原子”约束。

要完成“获取绝对最新值”,您需要执行强制硬件一致性协议锁定的操作(lockx86_64 上的指令)。如果您查看汇编输出,这就是原子比较和交换操作的作用。