[2026] C++ mutex for Race Conditions | Order Counter Bugs Through lock_guard
이 글의 핵심
Fix C++ data races with std::mutex, lock_guard, unique_lock, and scoped_lock. Deadlock avoidance, shared_mutex for readers/writers, and production patterns for thread-safe code.
Introduction: Why did the counter break?
“Order counts don’t match the database”
On event day, multiple worker threads incremented one shared counter. After the batch, totals were tens of thousands off from the DB.
Cause: plain int counter + counter++ from many threads → lost updates (data race). Fix: std::atomic
// 실행 예제 sequenceDiagram participant T1 as Thread 1 participant M as mutex participant T2 as Thread 2 T1->>M: lock() M-->>T1: acquired T2->>M: lock() Note over T2: blocked T1->>M: critical section T1->>M: unlock() M-->>T2: acquired T2->>M: critical section
After reading:
- Tell race condition vs data race
- Protect sections with std::mutex, lock_guard, unique_lock
- Avoid deadlock with ordering /
std::lock/scoped_lock - Use RAII so locks release on exceptions
Table of contents
- Race condition and data race
- Bug scenarios
- std::mutex usage
- lock_guard and unique_lock
- Avoiding deadlock
- Common mistakes
- Performance notes
- Best practices
- Production patterns
- Patterns
1. Race condition and data race
Race condition: outcome depends on scheduling order. Data race (C++): concurrent unsynchronized access where at least one is a write → undefined behavior. Mutex serializes access to shared data so read-modify-write sequences don’t interleave incorrectly.
2. Bug scenarios (summary)
- Stock / check-then-act: guard both check and update with one lock
- Log buffer:
string +=from many threads → protect with mutex - Work queue:
emptythenfront/popmust be one atomic operation → mutex + oftencondition_variable - Hit/miss counters: update related stats under one lock for consistent snapshots
- Config map: concurrent read/write on
std::map→shared_mutexor external mutex
3. std::mutex — basic usage
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// Paste: g++ -std=c++17 -pthread -o mutex_safe mutex_safe.cpp && ./mutex_safe
#include <mutex>
#include <thread>
#include <iostream>
int counter = 0;
std::mutex counter_mutex;
void safeIncrement() {
for (int i = 0; i < 100000; ++i) {
std::lock_guard<std::mutex> lock(counter_mutex);
++counter;
}
}
int main() {
std::thread t1(safeIncrement);
std::thread t2(safeIncrement);
t1.join();
t2.join();
std::cout << "counter=" << counter << "\n";
return 0;
}
Prefer lock_guard / unique_lock over raw lock()/unlock() so exceptions can’t leave the mutex locked.
try_lock: non-blocking attempt; if you lock, you must unlock (RAII still best).
4. lock_guard and unique_lock
RAII: lock in constructor, unlock in destructor.
unique_lock: can unlock()/lock() mid-scope—needed for condition_variable.
scoped_lock (C++17): lock multiple mutexes deadlock-free:
std::scoped_lock lock(m1, m2);
5. Avoiding deadlock
Cause: two threads lock A then B vs B then A. Fixes:
- Global lock order (always A then B)
- std::lock(lock1, lock2) with
defer_lock+unique_lock - std::scoped_lock(m1, m2) (recommended on C++17+)
- Minimize critical section—no I/O inside locks
6. Common mistakes
returnbeforeunlockwith manual lock/unlock → use RAII- Mutex far from data → easy to access data unlocked → encapsulate
- Double-lock same
std::mutexin one thread → deadlock (non-recursive) - Callbacks under lock → can re-enter or block on other locks → call callbacks outside the lock
7. Performance
| Approach | When |
|---|---|
mutex | Multiple variables, invariants across fields |
atomic | Single counters/flags, no invariants with other vars |
| Lock-free | Expert-only; hard to get right |
| Keep lock duration minimal; don’t hold mutexs across I/O. |
8. Best practices
- Prefer RAII (
lock_guard,unique_lock,scoped_lock) - Encapsulate data + mutex in a type
- Use
shared_mutexwhen reads dominate - Use ThreadSanitizer:
g++ -fsanitize=thread -g
9. Production patterns
- Thread-safe queue wrapper: all methods lock internally
- shared_mutex:
shared_lockfor read,unique_lockfor write - Snapshot publish:
atomic_store/atomic_loadofshared_ptr<const Data>for rare writes
10. Patterns
Bundle data + mutex in one struct; expose only thread-safe methods.
Implementation checklist
- Shared state protected by mutex or atomic
- RAII locks
- Minimal critical sections
- Multi-lock:
scoped_lockor fixed order -
find/remove+eraseidiom for containers
Related posts
Keywords
C++ mutex, lock_guard, unique_lock, shared_mutex, scoped_lock, race condition, deadlock, critical section, data race
Summary
- Data race ⇒ UB; synchronize with mutex/atomic/correct algorithms
- lock_guard for simple scopes; unique_lock for
condition_variableor mid-scope unlock - scoped_lock for multiple mutexes
- Deadlocks: consistent ordering or
std::lock/scoped_lockNext: condition_variable
FAQ
When is this useful?
A. Every multi-threaded C++ program with shared mutable data—servers, games, UI backends.
Read first?
A. Thread basics, then series index.
One-line summary: Wrap shared data in mutex + RAII; avoid deadlocks with scoped_lock and lock order discipline.