본문으로 건너뛰기
Previous
Next
C++ Multithreading Crashes: Data Races, mutex, atomic,

C++ Multithreading Crashes: Data Races, mutex, atomic,

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::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

#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)

  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.


자주 묻는 질문 (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++, Multithreading, Data race, Mutex, Atomic, ThreadSanitizer, Concurrency 등으로 검색하시면 이 글이 도움이 됩니다.