C++
Data races
Learn data races and how to prevent them.
Data race
A data race occurs when two threads access the same memory at the same time, at least one of them writes, and there is no synchronization between them. The C++ standard says this is undefined behavior.
Example: if two threads do counter++ on an int counter = 0;, some increments may be lost; the result may be 1 instead of 2. The fix: make the variable std::atomic<int> or protect it with a std::mutex.
Atomics prevent races
Because every access on a std::atomic variable is indivisible, no data race occurs. Even counter.fetch_add(1, std::memory_order_relaxed) called from two threads performs each increment fully; the result is consistent.
The relaxed ordering provides only atomicity, not cross-thread ordering. It is enough to prevent races; but it does not guarantee ordering of other variables. For ordering use acquire-release or seq_cst.
Exercise Prevent races with atomic fetch_add
Define a global std::atomic<int> counter{0};. In main call counter.fetch_add(1, std::memory_order_relaxed); twice and write std::cout << counter.load();.
Show example solution
#include <iostream>
#include <atomic>
std::atomic<int> counter{0};
int main() {
counter.fetch_add(1, std::memory_order_relaxed);
counter.fetch_add(1, std::memory_order_relaxed);
std::cout << counter.load();
return 0;
}
Test yourself
-
When does a data race occur?
int counter = 0;
// thread 1: counter++
// thread 2: counter++
- Two threads access different variables
- Two threads access the same atomic variable
- Two threads lock the same mutex
- Two threads access the same variable, at least one writes, no synchronization
Show answer
Two threads access the same variable, at least one writes, no synchronization — Shared memory, a write, and missing synchronization together form a data race.
-
Complete the mutex lock
std::lock_guard<std::mutex> lk(____);
Show answer
std::lock_guard<std::mutex> lk(mtx);
-
Match each tool to its role
- data race
- Undefined behavior
- std::atomic<int>
- Atomic access, no race
- std::mutex
- Protects with locking
- fetch_add
- Indivisible increment
Next lesson: Acquire-release