I ran following code many times but why the result for prefix increment , fetch_add() shows the correct result while with add operation (+), it prints the wrong result?
我多次运行以下代码,但为什么前缀增量的结果,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 个解决方案
#1
29
++cnt
and cnt.fetch_add(1)
are truly atomic operations. One thread is blocked while the other thread reads, increments, and updates the value. As such, the two threads cannot step on each other's toes. Access to cnt
is fully serialized, and the final result is as you would expect.
++ cnt和cnt.fetch_add(1)是真正的原子操作。一个线程被阻塞,而另一个线程读取,递增和更新该值。因此,两个线程不能互相踩到脚趾。对cnt的访问是完全序列化的,最终结果如您所料。
cnt = cnt+1;
is not fully atomic. It involves three separate operations, only two of which are atomic, but one is not. By the time a thread has atomically read the current value of cnt
and made a copy of it locally, the other thread is no longer blocked and can freely modify cnt
at will while that copy is being incremented. Then, the assignment of the incremented copy back to cnt
is done atomically, but will be assigning a stale value if cnt
has already been modified by the other thread. So the final result is random and not what you would expect.
cnt = cnt + 1;不是完全原子的。它涉及三个独立的操作,其中只有两个是原子的,但一个不是。当一个线程原子地读取cnt的当前值并在本地制作它的副本时,另一个线程不再被阻塞,并且可以在该副本递增时随意*地修改cnt。然后,将递增的副本分配回cnt以原子方式完成,但如果cnt已被其他线程修改,则将分配过时值。所以最终的结果是随机的而不是你所期望的。
#2
10
cnt = cnt+1
cnt = cnt + 1
This is not an atomic operation. This first loads cnt
in one atomic operation, then does the addition and finally stores the result in another atomic operation. However, the value can be changed after loading which can be overwritten by final store which leads to wrong end result.
这不是原子操作。这首先在一个原子操作中加载cnt,然后进行加法并最终将结果存储在另一个原子操作中。但是,加载后可以更改该值,最终存储可能会覆盖该值,从而导致错误的最终结果。
The other two are atomic operations and thus avoid such race condition.
另外两个是原子操作,因此避免了这种竞争条件。
Note that, operator ++, --, +=, -=, &=, |=, ^=
are overloaded in std::atomic
to provide atomic operations.
请注意,operator ++, - ,+ =, - =,&=,| =,^ =在std :: atomic中重载以提供原子操作。
#3
0
operator ++ is not a single operation but 3 operations load add store, and for ex on arm64 single load or store dosn't generate any data fence, data memory barier. for ex atomic_add 1 is a bunch of code with aquire/release semantics
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
where operator ++ will cause race condition if simulateusly used by 2 threads
如果由2个线程模拟使用,则operator ++将导致竞争条件
ldr x8, [x0]
add x8, x8, #1 // =1
str x8, [x0]
#1
29
++cnt
and cnt.fetch_add(1)
are truly atomic operations. One thread is blocked while the other thread reads, increments, and updates the value. As such, the two threads cannot step on each other's toes. Access to cnt
is fully serialized, and the final result is as you would expect.
++ cnt和cnt.fetch_add(1)是真正的原子操作。一个线程被阻塞,而另一个线程读取,递增和更新该值。因此,两个线程不能互相踩到脚趾。对cnt的访问是完全序列化的,最终结果如您所料。
cnt = cnt+1;
is not fully atomic. It involves three separate operations, only two of which are atomic, but one is not. By the time a thread has atomically read the current value of cnt
and made a copy of it locally, the other thread is no longer blocked and can freely modify cnt
at will while that copy is being incremented. Then, the assignment of the incremented copy back to cnt
is done atomically, but will be assigning a stale value if cnt
has already been modified by the other thread. So the final result is random and not what you would expect.
cnt = cnt + 1;不是完全原子的。它涉及三个独立的操作,其中只有两个是原子的,但一个不是。当一个线程原子地读取cnt的当前值并在本地制作它的副本时,另一个线程不再被阻塞,并且可以在该副本递增时随意*地修改cnt。然后,将递增的副本分配回cnt以原子方式完成,但如果cnt已被其他线程修改,则将分配过时值。所以最终的结果是随机的而不是你所期望的。
#2
10
cnt = cnt+1
cnt = cnt + 1
This is not an atomic operation. This first loads cnt
in one atomic operation, then does the addition and finally stores the result in another atomic operation. However, the value can be changed after loading which can be overwritten by final store which leads to wrong end result.
这不是原子操作。这首先在一个原子操作中加载cnt,然后进行加法并最终将结果存储在另一个原子操作中。但是,加载后可以更改该值,最终存储可能会覆盖该值,从而导致错误的最终结果。
The other two are atomic operations and thus avoid such race condition.
另外两个是原子操作,因此避免了这种竞争条件。
Note that, operator ++, --, +=, -=, &=, |=, ^=
are overloaded in std::atomic
to provide atomic operations.
请注意,operator ++, - ,+ =, - =,&=,| =,^ =在std :: atomic中重载以提供原子操作。
#3
0
operator ++ is not a single operation but 3 operations load add store, and for ex on arm64 single load or store dosn't generate any data fence, data memory barier. for ex atomic_add 1 is a bunch of code with aquire/release semantics
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
where operator ++ will cause race condition if simulateusly used by 2 threads
如果由2个线程模拟使用,则operator ++将导致竞争条件
ldr x8, [x0]
add x8, x8, #1 // =1
str x8, [x0]