Data races
- C++ course
- Expert
- Lesson 16/47
- 5 min
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.
Practice 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();.
Starter code
#include <iostream>
#include <atomic>
// Define the atomic counter here
int main() {
// Atomically increment counter twice and print it
return 0;
}Sample 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;
}Expected output
2
Questions and answers
Review the lesson questions together with their answers. To test yourself, solve the lesson interactively.
1 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 Correct answer
Explanation: Shared memory, a write, and missing synchronization together form a data race.
2 Complete the mutex lock
std::lock_guard<std::mutex> lk(____);
std::lock_guard<std::mutex> lk(mtx);
Explanation: The mtx mutex is locked by the lock_guard.
3 Match each tool to its role
| Term | Meaning |
|---|---|
data race |
Undefined behavior |
std::atomic<int> |
Atomic access, no race |
std::mutex |
Protects with locking |
fetch_add |
Indivisible increment |