CodeBlanks Logo Icon CodeBlanks

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.

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

main.cpp
#include <iostream>
#include <atomic>

// Define the atomic counter here

int main() {
    // Atomically increment counter twice and print it
    return 0;
}

Sample solution

main.cpp
#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?

main.cpp
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

Question
std::lock_guard<std::mutex> lk(____);
Answer
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

All lessons in Expert