我是否错误地假设 atomic::load 也应该充当内存屏障以确保所有以前的非原子写入都将被其他线程看到?
为了显示:
volatile bool arm1 = false;
std::atomic_bool arm2 = false;
bool triggered = false;
线程1:
arm1 = true;
//std::std::atomic_thread_fence(std::memory_order_seq_cst); // this would do the trick
if (arm2.load())
triggered = true;
线程2:
arm2.store(true);
if (arm1)
triggered = true;
我预计在执行两个“触发”之后都是真的。请不要建议将 arm1 设为原子,重点是探索 atomic::load 的行为。
虽然我不得不承认我并不完全理解内存顺序的不同宽松语义的正式定义,但我认为顺序一致的顺序非常简单,因为它保证“存在一个单一的总顺序,其中所有线程都观察所有修改以相同的顺序。” 对我来说,这意味着具有 std::memory_order_seq_cst 的默认内存顺序的 std::atomic::load 也将充当内存围栏。以下“顺序一致的排序”下的声明进一步证实了这一点:
完全顺序排序需要所有多核系统上的完整内存栅栏 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();
}
使用 gcc 这看起来像:
load():
mov rax, QWORD PTR al[rip] ; <--- plain load here, no fence or xchg
ret
main:
call load()
xor eax, eax
ret
我将省略本质上相同的 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}
这不是一个学术问题,它在我们的代码中导致了一个微妙的竞争条件,这让我对 std::atomic 的行为的理解产生了疑问。