Expert20 minC++ Fundamentals
UpdatedAug 4, 2026
Edit

Memory Model and Data Races

CONCEPTS:C++ Memory Model and Data Races

Question Variations

  • "What is a data race in C++ and why is it serious?"
  • "When should you use a mutex versus `std::atomic`?"
  • "What does acquire-release ordering provide?"
  • "Does `volatile` make a shared variable thread-safe?"

Why This Is Asked

Concurrency bugs in C++ can be undefined behavior rather than merely inconsistent results. This question tests whether you can identify a data race, use synchronization to establish visibility, and avoid reaching for atomics when a mutex gives a clearer correct design.

Key Concepts

  • A data race is conflicting unsynchronized access to the same non-atomic memory from multiple threads.
  • A data race is undefined behavior in C++.
  • Mutex lock/unlock operations establish synchronization between threads.
  • Atomics support low-level synchronization with explicit memory-order trade-offs.

Question Variations

  • “What is a data race in C++ and why is it serious?”
  • “When should you use a mutex versus std::atomic?”
  • “What does acquire-release ordering provide?”
  • “Does volatile make a shared variable thread-safe?”

Answers by Technology

+ Add Variant
C++Improve this answer ✏️

Expected Answer (C++23)

A data race occurs when threads access the same memory concurrently, at least one access writes, and the accesses are not synchronized. In C++, this is undefined behavior, so the compiler and CPU may make assumptions that invalidate intuitive source-level reasoning.

Use a mutex for ordinary shared mutable state. It protects a critical section and establishes the necessary ordering when one thread unlocks and another locks the same mutex. Use atomics only when a simple atomic variable or a carefully designed lock-free protocol is genuinely required. volatile does not provide thread safety.

#include <mutex>

class Counter {
public:
    void increment() {
        std::lock_guard<std::mutex> lock(mutex_);
        ++value_;
    }

    int value() const {
        std::lock_guard<std::mutex> lock(mutex_);
        return value_;
    }

private:
    mutable std::mutex mutex_;
    int value_ = 0;
};

Why It Matters

Correct synchronization protects data integrity and makes concurrent code portable across optimizers and hardware. A design that appears to work in a debug build can fail unpredictably once a data race is present.

Common Mistakes

  • Protecting writes but not reads: A read concurrent with a write is still a data race.
  • Using volatile for inter-thread communication: It is for special memory access behavior, not synchronization.
  • Replacing a simple mutex with complicated atomics prematurely: Incorrect memory ordering is difficult to test and review.

Follow-up Questions

  • What does std::lock_guard guarantee? (Answer: It unlocks the mutex automatically when the guard leaves scope.)
  • What is an atomic operation useful for? (Answer: Simple shared flags or counters where a single atomic operation expresses the needed synchronization.)