C++ Multithreading Crashes: Data Races, mutex, atomic,
이 글의 핵심
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](/en/blog/cpp-error-05-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.
// 변수 선언 및 초기화
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
#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
#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)
- Data races: mutex & atomic
- Multithreading overview
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.
More related posts
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. Fix intermittent multithreaded crashes: data races vs race conditions, std::mutex, atomics, false sharing basics, condit… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.
Q. 선행으로 읽으면 좋은 글은?
A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.
Q. 더 깊이 공부하려면?
A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- [C++ Undefined Behavior : Why Release-Only Crashes Happen and](/en/blog/cpp-error-10-undefined-behavior/
- [C++ Iterator Invalidation: “vector iterators incompatible”,](/en/blog/cpp-error-05-iterator-invalidation/
- C++ Data Race | ‘Mutex 대신 Atomic을 써야 하는 상황은?’ 면접 단골 질문 정리
- C++ Lock-Free 프로그래밍 실전 | CAS·ABA·메모리 순서·고성능 큐 [#34-3]
이 글에서 다루는 키워드 (관련 검색어)
C++, Multithreading, Data race, Mutex, Atomic, ThreadSanitizer, Concurrency 등으로 검색하시면 이 글이 도움이 됩니다.