Skip to content

How to Fix C++ Thread Safety Data Race Errors

DodaTech Updated 2026-06-24 2 min read

In this tutorial, you'll learn about How to Fix C++ Thread Safety Data Race Errors. We cover key concepts, practical examples, and best practices.

C++ data race errors occur when two or more threads access the same memory location concurrently, with at least one thread writing, and no synchronization. The C++ standard declares this undefined behavior.

Quick Fix

Wrong

int counter = 0;

auto increment = [&]() {
    for (int i = 0; i < 100000; ++i) {
        ++counter;  // data race!
    }
};

std::thread t1(increment);
std::thread t2(increment);
t1.join(); t2.join();

counter is not atomic and is not protected, causing a data race.

std::atomic<int> counter = 0;

auto increment = [&]() {
    for (int i = 0; i < 100000; ++i) {
        ++counter;  // atomic increment
    }
};

std::thread t1(increment);
std::thread t2(increment);
t1.join(); t2.join();
std::cout << counter;
200000

Fix with mutex

std::map<int, int> data;
std::mutex mtx;

void write(int k, int v) {
    std::lock_guard<std::mutex> lock(mtx);
    data[k] = v;
}

int read(int k) {
    std::lock_guard<std::mutex> lock(mtx);
    return data[k];
}

Fix with reader-writer lock

std::shared_mutex rwmutex;

void write() {
    std::unique_lock lock(rwmutex);
    // exclusive write access
}

void read() {
    std::shared_lock lock(rwmutex);
    // shared read access
}

Prevention

  • Use std::atomic<T> for simple shared counters and flags.
  • Use std::mutex with std::lock_guard for complex shared data.
  • Use std::shared_mutex for read-heavy workloads.
  • Use thread-local storage for per-thread data.
  • Run ThreadSanitizer (-fsanitize=thread) during testing.

DodaTech Tools

Doda Browser's C++ thread analyzer detects data races and lock contention in concurrent code. DodaZIP archives ThreadSanitizer reports. Durga Antivirus Pro detects race conditions that could be exploited for privilege escalation.

Common Mistakes with thread safety

  1. Forgetting deriving (Show, Eq) on custom data types needed for debugging
  2. Placing the wildcard pattern first in case expressions, making all subsequent patterns unreachable
  3. Using head and tail instead of pattern matching, causing runtime errors on empty lists

These mistakes appear frequently in real-world CPP code. DodaTech's contributors have identified these patterns through analysis of open-source projects and production systems.

Practice Exercise

Write a pure function that safely divides two integers using Maybe, then test it with edge cases like division by zero and negative numbers.

This exercise reinforces the concepts covered in this guide. Try implementing it before checking online solutions.

FAQ

What is the difference between `std::atomic` and `std::mutex`?

std::atomic<T> provides lock-free atomic operations on a single variable. std::mutex provides mutual exclusion for critical sections with multiple operations. Use atomics for simple counters and flags; use mutexes for complex data structures.

What is a data race in C++?

A data race occurs when two threads access the same memory location concurrently, at least one access is a write, and there is no synchronization. Data races are undefined behavior in C++.

How does ThreadSanitizer detect data races?

ThreadSanitizer instruments memory accesses at compile time and tracks happens-before relationships at runtime. It reports a warning when two unsynchronized accesses occur on the same memory location with at least one write.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro