6

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

#include <atomic>

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

我在 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              

同样在 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              

我只是不明白:为什么变量的轻松增量int需要lock前缀?

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


* 我用/O2with/NoDefaultLib来修剪它并去掉不必要的 C 运行时代码,但这与问题无关。

4

3 回答 3

6

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

想象一下没有锁的情况。

v = 0;

然后我们生成 100 个线程,每个线程都使用以下命令:

v++;

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

但是对于 std::atomic,所有操作都是原子的,无论std::memory_order设置如何。唯一的问题是它们将以什么顺序发生。memory_order_relaxed仍然保证原子性,它可能与附近发生的任何其他事情无关,即使在相同的值上运行。

于 2015-02-27T08:34:47.367 回答
1

原子操作,即使有宽松的顺序,仍然必须atomic

即使当前 CPU 上的某些操作没有前缀的原子操作lock(提示:它们不是,由于多核缓存),对于未来的 CPU 也不能保证。

仅仅因为您想依赖不属于汇编规范一部分的功能优化二进制文件中的一个字节(因此不能保证在未来的 x86_64架构)

当然,在这种情况下,多核系统很普遍,因此您实际上需要一个lock前缀才能使其在当前 CPU 上工作。请参阅“int num”的 num++ 是否是原子的?

于 2020-06-17T23:07:57.563 回答
-1

首先,作为参考,考虑一个正常的分配。它在 Intel/64 上生成以下内容:

// v = 10;
000000014000E0D0  mov         eax,0Ah  
000000014000E0D5  xchg        eax,dword ptr [v (014001BCDCh)]  

然后考虑一个轻松的分配:

// v.store(10, std::memory_order_relaxed);
000000014000E0D0  mov         dword ptr [v (014001BCDCh)],0Ah 

现在,std::atomic::fetch_add()这是一个读取-修改-写入操作,以“肮脏”的方式执行此操作几乎没有意义。默认情况下,您std::memory_order_seq_cst按照http://en.cppreference.com/w/cpp/atomic/atomic/fetch_add 获取。所以,我认为,为此生成一条本机指令是有意义的。至少在便宜的 Intel/64 上:

// v.fetch_add(1, std::memory_order_relaxed)
000000014000E0D0  mov         eax,1  
000000014000E0D5  lock xadd   dword ptr [v (014001BCDCh)],eax  

毕竟,您可以通过显式编写编译器必须遵守的两个操作来实现您想要的:

// auto x = v.load(std::memory_order_relaxed);
000000014000E0D0  mov         eax,dword ptr [v (014001BCDCh)]  

// ++x;
000000014000E0D6  inc         eax  

//v.store(x, std::memory_order_relaxed);
000000014000E0D8  mov         dword ptr [v (014001BCDCh)],eax  
于 2014-07-31T06:36:17.607 回答