++,add操作和atomic()中的fetch_add()有什么区别

时间:2018-08-29 05:04:17

标签: c++ c++11

我多次执行以下代码,但是为什么前缀递增的结果fetch_add()会显示正确的结果,而使用加号操作(+)却会输出错误的结果?

#include <iostream>
#include <mutex>
#include <future>
using namespace std;
atomic <int> cnt (0);
void fun()
{
    for(int i =0; i <10000000 ; ++i)
    {
       //++cnt; // print the correct result 20000000 
       //cnt = cnt+1; // print wrong result, arbitrary numbers 
       cnt.fetch_add(1); //  print the correct result 20000000 
    }
}
int main()
{
    auto fut1 = async(std::launch::async, fun);
    auto fut2 = async(std::launch::async, fun);
    fut1.get();
    fut2.get();
    cout << "value of cnt: "<<cnt <<endl;

} 

3 个答案:

答案 0 :(得分:29)

++cntcnt.fetch_add(1)是真正的原子操作。一个线程被阻塞,而另一个线程读取,递增和更新该值。因此,两个线程无法踩到彼此的脚趾。对cnt的访问已完全序列化,最终结果与您期望的一样。

cnt = cnt+1;并非完全原子。它涉及三个单独的操作,其中只有两个是原子操作,但不是一个。等到一个线程自动读取cnt的当前值并在本地对其进行复制时,另一个线程便不再受阻,可以随意修改cnt而该副本正在递增。然后,将递增的 copy 分配回cnt的过程是原子完成的,但是如果cnt已被另一个线程修改,则将分配陈旧的值。因此,最终结果是随机的,而不是您所期望的。

答案 1 :(得分:10)

cnt = cnt+1

这不是原子操作。这首先在一个原子操作中加载cnt,然后进行加法运算,最后将结果存储在另一个原子操作中。但是,该值可以在加载后更改,而最终存储可能会覆盖该值,从而导致错误的最终结果。

另外两个是原子操作,因此避免了这种竞争情况。

请注意,运算符++, --, +=, -=, &=, |=, ^=std::atomic中被重载以提供原子操作。

答案 2 :(得分:0)

operator ++不是单个操作,而是3个操作加载存储,而对于arm64上的ex,单个加载或存储不会生成任何数据屏障,数据存储空间更大。 例如前atomic_add 1是一堆具有aquire / release语义的代码

.LBB2_1:            
ldaxr   x8, [x0] //load exclusive register with aquire 
add x8, x8, #1  
stlxr   w9, x8, [x0] //store with rlease
cbnz    w9, .LBB2_1 //if another thread changed value, try again

如果2个线程模拟使用运算符++,则会导致竞争情况

ldr x8, [x0]
add x8, x8, #1              // =1
str x8, [x0]