[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::mutexpatternsstd::atomicbasics- ThreadSanitizer
- Ten common concurrency bugs (sketches)
Table of contents
- What is a data race?
- Synchronizing with mutex
- Atomic variables
- ThreadSanitizer
- Ten common bugs
- 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)
- Unsynchronized shared counter →
atomicormutex - Concurrent
vectormutation → protect all writers (and readers if writers exist) - False sharing → separate hot atomics to different cache lines (
alignas(64)patterns where justified) - Broken double-checked locking →
std::call_onceor static locals (since C++11) - Condition variable without predicate loop → handle spurious wakeups with
wait(lock, pred) - Reading shared state while another thread writes without synchronization
- Concurrent
unique_ptrreassignment without synchronization - Iterator invalidation across threads (one thread mutates container while another iterates)
- Non-thread-safe singleton patterns →
call_once/ Meyers singleton - 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
| Scenario | Tool |
|---|---|
| Simple counters/flags | atomic |
| Multi-field invariants | mutex |
| Read-mostly maps | shared_mutex (careful) |
| One-time init | std::call_once |
Rules
- No unsynchronized data races on non-atomic objects.
- Prefer scoped_lock for multiple mutexes.
- TSan on threaded test suites.
- Minimize critical sections; do not release locks mid-iteration if it invalidates iterators.
- Document lock ordering for reviewers.
Related posts (internal)
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.