Alok
Alok

Reputation: 2035

what is the difference between ++, add operation and fetch_add() in atomic()

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

Answers (3)

Artur Bac
Artur Bac

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

Remy Lebeau
Remy Lebeau

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

taskinoor
taskinoor

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

Related Questions