给定一个 C++ 代码片段:
\n\nint a = 0;\natomic<int> b{0};\n\nThread 1 \na = 1;\nb.store(1,memory_order_release);\n\nThread 2\nwhile(!b.load(memory_order_acquire)); \nassert(a==1);\nRun Code Online (Sandbox Code Playgroud)\n\n我们知道断言永远不会触发。
\n\n另一方面,golangatomic.Store隐含内存屏障的 xchg 指令,因此它可以导致与 c++11 一样的 memory_order_release 语义。
\n\n//go:noescape\nfunc Store(ptr *uint32, val uint32)\nTEXT runtime\xe2\x88\x95internal\xe2\x88\x95atomic\xc2\xb7Store(SB), NOSPLIT, $0-12\n MOVQ ptr+0(FP), BX\n MOVL val+8(FP), AX\n XCHGL AX, 0(BX)\n RET\nRun Code Online (Sandbox Code Playgroud)\n\n然而, atomic.Load的实现是纯go代码,这意味着汇编时只是mov指令。
\n\n//go:nosplit\n//go:noinline\nfunc Load(ptr *uint32) uint32 {\n return *ptr\n}\nRun Code Online (Sandbox Code Playgroud)\n\n那么,golangatomic.Load有acquire语义吗?
\n如果它是如何工作的,如果不是,如何确保内存排序或使 a=1 可见?
在 x86/amd64 等强有序架构上,获取加载和释放存储只是常规加载和存储。为了使它们原子化,您需要确保内存与操作数大小对齐(Go 中自动对齐),并且编译器不会以不兼容的方式对它们重新排序,或者优化它们(例如,重用寄存器中的值)从记忆中读出它。)
Go 原子 Load* 和 Store* 函数是顺序一致的。这是一种更强大的内存排序形式,即使在 x86/amd64 上也需要内存栅栏(或具有隐式内存栅栏的指令)。
引用 rsc:
Go 的原子保证原子变量之间的顺序一致性(行为类似于 C/C++ 的 seqconst 原子),并且您不应该对给定的内存字混合原子和非原子访问。