在 atomic() 中 ++、add operation 和 fetch_add() 有什么区别



我多次运行以下代码,但为什么前缀增量的结果,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;
} 

++cntcnt.fetch_add(1)是真正的原子操作。 一个线程被阻塞,而另一个线程读取、递增和更新值。因此,两条线不能踩到对方的脚趾。 对cnt的访问是完全序列化的,最终结果如您所料。

cnt = cnt+1;不是完全原子的。 它涉及三个独立的操作,其中只有两个是原子的,但一个不是。 当一个线程以原子方式读取cnt的当前值并在本地创建它的副本时,另一个线程不再被阻塞,并且可以在该副本递增时随意修改cnt。 然后,将递增的副本重新分配给cnt以原子方式完成,但如果cnt已被其他线程修改,则将分配一个过时的值。 所以最终结果是随机的,而不是你所期望的。

cnt = cnt+1

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

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

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

运算符++不是单个操作,而是3个操作加载添加存储,例如ARM64上的单个加载或存储不会生成任何数据围栏,数据存储器更宽。 对于 ex atomic_add 1 是一堆具有获取/发布语义的代码

.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]

最新更新