Reputation: 2035
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?
#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;
}
Upvotes: 26
Views: 5118
Reputation: 135
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
.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
ldr x8, [x0]
add x8, x8, #1 // =1
str x8, [x0]
Upvotes: 0
Reputation: 597016
++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+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.
Upvotes: 32
Reputation: 46037
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.
The other two are atomic operations and thus avoid such race condition.
Note that, operator ++, --, +=, -=, &=, |=, ^=
are overloaded in std::atomic
to provide atomic operations.
Upvotes: 12