std::atomic
A mutex protects any section of code, but for a single number it is overkill. Every change needs a lock and an unlock, and the threads wait for each other. For simple variables, the <atomic> header has the std::atomic template:
#include <atomic>
#include <iostream>
#include <thread>
int main() {
std::atomic<int> hits{0};
std::thread worker([&hits]() { hits += 5; });
hits += 2;
worker.join();
std::cout << hits << '\n'; // 7
}Both threads change hits at the same time, and the program has no mutex. Still, there is no data race here, and the total is always 7.
Atomic operations
std::atomic<int> holds an ordinary int but performs every action on it as a whole. Its ++, --, +=, -=, reads and assignments are atomic operations. Another thread sees such an operation either not yet started or already finished, but never half done. That is why simultaneous changes are not lost.
The counter from the lesson on data races becomes correct if you replace int counter = 0; with std::atomic<int> counter{0};. No mutex is needed then, and the program always prints 200000.
std::atomic works for other simple types too, and variables of such types are called atomic. A stop flag is usually declared as std::atomic<bool> stop{false};. One thread assigns stop = true, and another checks stop in a loop and leaves it once the flag is set.
Atomic variable or mutex
An atomic variable protects one value and one operation on it. A mutex protects a group of actions: say, changing two variables consistently, or checking a value and then changing it. Two separate atomic variables do not help here: between two operations, another thread can see an intermediate state.
For a single counter or flag, an atomic variable is usually cheaper than a mutex. For everything else, where there is more than one action, you choose a mutex.
Common mistake
An atomic variable is incremented in two steps:
counter = counter + 1; // ✗There is no data race here: both the read of counter and the write are atomic. But they are two separate operations, and another thread can change counter between them. The other thread's increment is then lost, and the total can end up below 200000. Write it as one operation: ++counter or counter += 1.