[2026] C++ Multithreading Crashes: Data Races, mutex, atomic, and ThreadSanitizer

[2026] C++ Multithreading Crashes: Data Races, mutex, atomic, and ThreadSanitizer

이 글의 핵심

Fix intermittent multithreaded crashes: data races vs race conditions, std::mutex, atomics, false sharing basics, condition variables, and ThreadSanitizer (-fsanitize=thread).

Data races are UB (undefined behavior). Iterator misuse across threads overlaps with iterator invalidation.

Introduction: “Multithreaded code crashes sometimes”

“Single-threaded version was fine”

The most common serious bug in concurrent C++ is a data race: unsynchronized access to shared mutable state. 아래 코드는 cpp를 사용한 구현 예제입니다. 반복문으로 데이터를 처리합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// 변수 선언 및 초기화
int counter = 0;
void worker() {
    for (int i = 0; i < 1'000'000; ++i) {
        ++counter;  // data race
    }
}

This article covers:

  • Data races vs informal “race conditions”
  • std::mutex patterns
  • std::atomic basics
  • ThreadSanitizer
  • Ten common concurrency bugs (sketches)

Table of contents

  1. What is a data race?
  2. Synchronizing with mutex
  3. Atomic variables
  4. ThreadSanitizer
  5. Ten common bugs
  6. Summary

1. What is a data race?

A data race (in the standard sense) requires conflicting accesses without synchronization. It is undefined behavior. Typical outcomes: torn reads/writes, crashes, or “lucky” passes.

2. mutex

아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <mutex>
int counter = 0;
std::mutex mtx;
void worker() {
    for (int i = 0; i < 1'000'000; ++i) {
        std::lock_guard<std::mutex> lock(mtx);
        ++counter;
    }
}

Deadlock avoidance

Always acquire multiple mutexes in a consistent global order, or use std::scoped_lock (C++17) / std::lock to lock several mutexes atomically.

3. Atomics

아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

#include <atomic>
std::atomic<int> counter{0};
void worker() {
    for (int i = 0; i < 1'000'000; ++i) {
        ++counter;  // atomic RMW
    }
}

Rule of thumb: one simple counter or flag → often atomic. Multiple fields that must move together → mutex.

4. ThreadSanitizer

g++ -g -fsanitize=thread -std=c++17 -o myapp main.cpp
./myapp

TSan reports racing lines with stacks—fix the synchronization at those sites.

5. Ten common bugs (overview)

  1. Unsynchronized shared counteratomic or mutex
  2. Concurrent vector mutation → protect all writers (and readers if writers exist)
  3. False sharing → separate hot atomics to different cache lines (alignas(64) patterns where justified)
  4. Broken double-checked lockingstd::call_once or static locals (since C++11)
  5. Condition variable without predicate loop → handle spurious wakeups with wait(lock, pred)
  6. Reading shared state while another thread writes without synchronization
  7. Concurrent unique_ptr reassignment without synchronization
  8. Iterator invalidation across threads (one thread mutates container while another iterates)
  9. Non-thread-safe singleton patterns → call_once / Meyers singleton
  10. Too-small critical sections split across logically atomic updates
    (See code blocks in the Korean article for concrete snippets; principles above map 1:1.)

Patterns: thread pool sketch & shared_mutex

A work queue with mutex + condition_variable is the standard producer/consumer pattern: hold the lock only to take/push tasks; execute work outside the lock. std::shared_mutex: many concurrent readers or one writer—ideal for read-heavy caches if invariants are simple.

Summary

Checklist

  • Every shared mutable object has a clear synchronization policy?
  • Reads synchronized when writes may occur?
  • Lock ordering documented to avoid deadlock?
  • Condition variables use predicates?
  • TSan runs on CI for threaded tests?

Tool choice

ScenarioTool
Simple counters/flagsatomic
Multi-field invariantsmutex
Read-mostly mapsshared_mutex (careful)
One-time initstd::call_once

Rules

  1. No unsynchronized data races on non-atomic objects.
  2. Prefer scoped_lock for multiple mutexes.
  3. TSan on threaded test suites.
  4. Minimize critical sections; do not release locks mid-iteration if it invalidates iterators.
  5. Document lock ordering for reviewers.


Keywords

multithreaded crash, data race, mutex, atomic, ThreadSanitizer, TSan, concurrency

Practical tips

  • Run TSan locally on any new concurrent code.
  • Treat intermittent failures as races until proven otherwise.
  • Write down lock order for multi-lock code paths.

Closing

Concurrent bugs are hard to reproduce; ThreadSanitizer turns many races into actionable reports. Pair clear ownership of shared state with mutex/atomic discipline. Next: Explore lock-free programming only after solid mutex-based designs and measurements justify it.

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3