Why program with this code sometimes prints "2" ?
int main() {
std::atomic<int> a;
a = 0;
std::thread t1([&]{++a;});
std::thread t2([&]{a++;});
std::thread t3([&]{
a = a.load() + 1;
});
t1.join();
t2.join();
t3.join();
if (a != 3) {
std::cout << "a: " << a << std::endl;
}
}
I've thought std::atomic
guarantees that all operations will be done atomically so writing here(incrementing) will use a memory barrier and we will have always 3
at the end of threads work. I've explored the code and found out that the problem thread is t3
but I can't understand why it is wrong.
Atomics as part of the C language are an optional feature that is available since C11. Their purpose is to ensure race-free access to variables that are shared between different threads. Without atomic qualification, the state of a shared variable would be undefined if two threads access it concurrently.
In C++11, there is finally a way to perform truly portable atomic loads and stores: the C++11 atomic library. Atomic loads and stores performed using the C++11 atomic library would even work on the imaginary computer above – even if it means the C++11 atomic library must secretly lock a mutex to make each operation atomic.
Only operations on data can ever be atomic. Additionally, the std::atomic<> types are designed in such a way where only atomic operations are meant to be applied to the data a type represents, and never to intermix atomic and non-atomic operations. The most basic atomic operations are loads and stores.
We can formulate it as a rule: Any time two threads operate on a shared variable concurrently, and one of those operations performs a write, both threads must use atomic operations.
t3
, unlike the two other threads, does not perform an atomic add. Instead, it atomically loads a
, performs the arithmetic (add 1) on a temporary, and atomically stores that new value back to a
. This overwrites a
regardless of atomic operations that might have happened in between.
So you can have the following scenario:
t1
or t2
atomically increment a
which is now equal to 1.t3
atomically loads 1.t1
or t2
atomically increment a
which is now equal to 2.t3
performs a non-atomic add on the previously loaded value and atomically stores back 2.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With