为什么memory_order_relaxed在x86上使用原子(锁定前缀)指令?

Meh*_*dad 6 c++ x86 atomic visual-c++ relaxed-atomics

在Visual C++ 2013上,当我编译以下代码时

#include <atomic>

int main()
{
    std::atomic<int> v(2);
    return v.fetch_add(1, std::memory_order_relaxed);
}
Run Code Online (Sandbox Code Playgroud)

我在x86上找回了以下程序集:

51               push        ecx  
B8 02 00 00 00   mov         eax,2 
8D 0C 24         lea         ecx,[esp] 
87 01            xchg        eax,dword ptr [ecx] 
B8 01 00 00 00   mov         eax,1 
F0 0F C1 01      lock xadd   dword ptr [ecx],eax 
59               pop         ecx  
C3               ret              
Run Code Online (Sandbox Code Playgroud)

在x64上类似:

B8 02 00 00 00    mov         eax,2 
87 44 24 08       xchg        eax,dword ptr [rsp+8] 
B8 01 00 00 00    mov         eax,1 
F0 0F C1 44 24 08 lock xadd   dword ptr [rsp+8],eax 
C3                ret              
Run Code Online (Sandbox Code Playgroud)

我根本就不明白:为什么变量的宽松增量int需要lock前缀?

这有什么原因,还是他们根本没有包括删除它的优化?


*我用/O2/NoDefaultLib来修剪它并摆脱不必要的C运行时代码,但这与问题无关.

Voi*_*tar 5

因为仍然需要锁才能成为原子锁;即使memory_order_relaxed对增量/减量的要求过于严格,也无法锁定。

想象一下没有锁的同一件事。

v = 0;
Run Code Online (Sandbox Code Playgroud)

然后我们产生100个线程,每个线程都带有以下命令:

v++;
Run Code Online (Sandbox Code Playgroud)

然后您等待所有线程完成,您期望v是什么?不幸的是,它可能不是100。假设一个线程加载了v = 23的值,并且在创建24之前,另一个线程也加载了23,然后也写出了24。因此,线程实际上彼此相反。这是因为增量本身不是原子的。当然,加载,存储,添加本身可能是原子的,但是递增是多个步骤,因此不是原子的。

但是使用std :: atomic时,无论std::memory_order设置如何,所有操作都是原子的。唯一的问题是它们将以什么顺序发生。memory_order_relaxed仍然保证原子性,就它附近发生的其他任何事情而言,即使按相同的值进行操作,也可能会乱序。

  • 许多非 x86 ISA 可以执行原子读-修改-写操作,而无需强制设置完整的内存屏障。例如,ARM 与许多 RISC ISA 一样,具有加载链接/存储条件。除非您使用屏障指令或 ARM64 加载获取,否则您将获得不排序其他内存操作的原子操作。正如您所说,**只是 x86 的一个怪癖,执行原子 RMW 操作的唯一方法也是完整的内存屏障**。 (3认同)