본문으로 건너뛰기
Abseil Code Review · 34/79

absl::Mutex — reader-writer·fairness·deadlock 검출

· Hawk · 4분 읽기

#한 줄 요약

absl::Mutex는 exclusive/shared lock을 한 타입에 통합하고, Conditional Critical Section, deadlock 검출, contention profiling, thread-safety 어노테이션을 모두 갖춘 동기화 primitive다. 표준의 std::mutex + std::shared_mutex + std::condition_variable을 한 묶음으로 대체한다.

#동기

표준 동기화는 책임이 흩어져 있다.

  • std::mutex — exclusive only
  • std::shared_mutex — reader-writer (C++17)
  • std::condition_variable — wait/notify (predicate를 외부 변수로 표현)
  • std::condition_variable_any — 다른 mutex와도

이들의 조합은 코드를 복잡하게 만든다. condition variable의 spurious wakeup, predicate loop 같은 함정도 매번 직접 처리.

absl::Mutex는 하나의 타입에서 모두 처리한다. 추가로 함수형 조건 표현(Await)을 지원해 condition_variable 패턴을 단순화한다.

표준 mutex 대비 acquire 지연을 시나리오별로 비교하면 다음과 같다.

absl::Mutex vs std::mutex 성능

#API와 사용법

#include "absl/synchronization/mutex.h"
absl::Mutex mu;
// exclusive
mu.Lock();
// ...
mu.Unlock();
// RAII
{
absl::MutexLock lock(&mu);
// ...
}
// shared (read)
mu.ReaderLock();
mu.ReaderUnlock();
// 또는
{
absl::ReaderMutexLock lock(&mu);
// ...
}

MutexLock은 exclusive RAII, ReaderMutexLock은 shared RAII. std::lock_guard/std::shared_lock을 mutex 종류와 맞춰 골라 쓰는 일이 없다.

#Conditional Critical Section

진정한 차별점은 Await 함수다.

class Queue {
public:
void Push(int x) {
absl::MutexLock l(&mu_);
items_.push_back(x);
}
int PopWaiting() {
absl::MutexLock l(&mu_);
mu_.Await(absl::Condition(this, &Queue::HasItem));
int x = items_.front();
items_.pop_front();
return x;
}
private:
bool HasItem() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu_) {
return !items_.empty();
}
absl::Mutex mu_;
std::deque<int> items_ ABSL_GUARDED_BY(mu_);
};

Await(Condition)는 condition이 true가 될 때까지 대기한다. predicate가 mutex 안에서 평가되므로 spurious wakeup이 없고 while loop이 필요 없다. condition variable 관용구가 완전히 사라진다.

상세는 Part 6-02 — Conditional Critical Section에서.

#std 비교

// std — condition variable 관용구
std::mutex mu;
std::condition_variable cv;
std::deque<int> q;
void Push(int x) {
{
std::lock_guard<std::mutex> l(mu);
q.push_back(x);
}
cv.notify_one();
}
int Pop() {
std::unique_lock<std::mutex> l(mu);
cv.wait(l, [&] { return !q.empty(); }); // predicate while loop
int x = q.front(); q.pop_front();
return x;
}
// absl — Await로 같은 동작
absl::Mutex mu;
std::deque<int> q;
void Push(int x) {
absl::MutexLock l(&mu);
q.push_back(x);
// notify 불필요 — Await가 자동
}
int Pop() {
absl::MutexLock l(&mu);
mu.Await(absl::Condition(+[](std::deque<int>* q) {
return !q->empty();
}, &q));
int x = q.front(); q.pop_front();
return x;
}

notify 호출이 사라진다. Await는 mutex가 unlock될 때마다 조건을 자동으로 재평가한다.

#thread-safety annotation

absl::Mutex는 clang의 -Wthread-safety와 결합된 매크로를 제공한다.

class Cache {
public:
std::string Get(absl::string_view key) ABSL_LOCKS_EXCLUDED(mu_);
private:
std::string LookupLocked() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu_);
absl::Mutex mu_;
absl::flat_hash_map<std::string, std::string> data_ ABSL_GUARDED_BY(mu_);
};

ABSL_GUARDED_BY(mu_)는 멤버 접근에 mu_ lock이 필요함을 컴파일러에 알린다. 위반은 컴파일 경고. 상세는 Part 6-05 — Mutex annotations.

#deadlock 검출

-DABSL_INTERNAL_USE_NONPROD_MUTEX 같은 빌드 옵션으로 lock 순서 그래프를 추적해 cycle을 검출한다. 일반적으로는 debug 빌드에서 켜고, release에서는 끈다.

ASSERTION FAILED: lock cycle detected:
thread T1: acquired mu_a then waited on mu_b
thread T2: acquired mu_b then waited on mu_a

production 추적은 contention profiler와 결합해 lock graph를 dump하기도 한다.

#contention profiler

abseil은 mutex contention을 sampling profile로 모은다.

absl::RegisterMutexProfiler(&MyProfiler);
// MyProfiler가 매 contention 이벤트에서 호출됨

production에서 hot mutex를 식별하는 데 쓴다. std::mutex로는 직접 wrapping 없이는 불가능.

#contention이 무엇을 의미하는가

profiler가 보여 주는 contention은 결국 wait 시간이다.

Lock contention timeline

CS 자체는 짧아도 자주 호출되면 대기 행렬이 길어진다. profiler의 hot mutex는 보통 CS가 긴 것이 아니라 호출 빈도가 너무 높은 것이다. 그래서 fix는 CS를 줄이는 게 아니라 lock을 쪼개거나 lock-free 자료구조로 옮기는 방향이 된다.

#fairness

absl::Mutex부분 fair 정책이다. 굶주린 writer가 너무 오래 대기하지 않도록 reader를 일정 시점 후에 막는다. 기본 std::shared_mutex는 fairness를 표준이 규정하지 않아 구현마다 다르다.

#코드 리뷰 포인트

1. std::mutex + std::condition_variable 패턴 → absl::Mutex + Await

// 회피 — notify 누락, spurious wakeup 함정
std::mutex mu;
std::condition_variable cv;
bool ready = false;
// producer
{
std::lock_guard<std::mutex> l(mu);
ready = true;
}
cv.notify_one(); // ← 누락하면 deadlock
// consumer
std::unique_lock<std::mutex> l(mu);
cv.wait(l, [&] { return ready; });
// Good
absl::Mutex mu;
bool ready = false;
// producer
{
absl::MutexLock l(&mu);
ready = true;
// notify 불필요
}
// consumer
absl::MutexLock l(&mu);
mu.Await(absl::Condition(&ready));

2. shared lock 적극 활용

read 빈도 >> write 빈도면 ReaderLock으로 처리량 증대.

class ConfigCache {
public:
std::string Get(absl::string_view key) {
absl::ReaderMutexLock l(&mu_);
auto it = data_.find(key);
return it == data_.end() ? "" : it->second;
}
void Set(std::string k, std::string v) {
absl::MutexLock l(&mu_);
data_[std::move(k)] = std::move(v);
}
private:
absl::Mutex mu_;
absl::flat_hash_map<std::string, std::string> data_ ABSL_GUARDED_BY(mu_);
};

3. annotation 일관 적용

annotation 한 군데만 적용하면 효과가 적다. 클래스 전체를 annotate.

#안티패턴

MutexLock 객체 무시

// 회피 — 임시 객체가 즉시 소멸, lock 해제됨
absl::MutexLock(&mu_); // 변수 이름 없음
DoWork(); // unlocked
// Good
absl::MutexLock l(&mu_);
DoWork();

MutexLock 같은 RAII 객체는 반드시 변수에 binding. clang -Wunused-variable로 잡힌다.

Await 안에서 lock acquire

mu_.Await(absl::Condition([&] {
other_mu_.Lock(); // 회피 — Await predicate 안에서 다른 lock
// ...
}));

Await predicate는 mutex가 잡힌 상태에서 자주 호출된다. 안에서 다른 lock을 잡으면 deadlock 위험.

Unlock 잊음

raw Lock/Unlock은 RAII 없이는 위험. 항상 MutexLock/ReaderMutexLock.

#정리

  • absl::Mutex = exclusive + shared + condition variable + annotation + profiler 통합.
  • Await(Condition)로 condition variable 관용구 제거.
  • thread-safety 매크로로 컴파일 타임 검증.
  • contention profiler, deadlock 검출 기본 제공.
  • shared/exclusive 모두 RAII는 MutexLock/ReaderMutexLock.

#다음 편

Part 6-02 — Conditional Critical Section에서 Await 패턴을 더 자세히 본다.

#관련 항목

Abseil Code Review · 35 of 79

  1. 1 Abseil Code Review — Google production-grade C++ 라이브러리 분석
  2. 2 Abseil 개요 — Google이 std를 보완한 이유
  3. 3 Abseil 설계 철학 — std 호환과 추가 기능의 균형
  4. 4 Abseil 빌드와 의존성 — Bazel vs CMake
  5. 5 Abseil LTS vs HEAD 릴리스 모델 분석
  6. 6 Abseil Versioning과 ABI 호환성 정책
  7. 7 Abseil 매크로 — ABSL_HAVE_*·ABSL_ATTRIBUTE_*
  8. 8 Abseil ABSL_PREDICT_TRUE/FALSE — branch hint
  9. 9 absl::LogSeverity — 로그 레벨 타입
  10. 10 Abseil type_traits — negation·conjunction·void_t
  11. 11 Abseil Conformance·Policy 분석
  12. 12 Abseil Memory utilities 분석
  13. 13 Abseil raw_logging — heap-free 로깅
  14. 14 Abseil thread_annotations — clang TSA 통합
  15. 15 absl::Status — exception-free error handling
  16. 16 absl::StatusOr<T> — 값 또는 에러
  17. 17 absl status_macros — ASSIGN_OR_RETURN·RETURN_IF_ERROR
  18. 18 absl::Status payload — 구조화된 에러 컨텍스트
  19. 19 absl::Status ↔ exception 변환 패턴
  20. 20 absl::string_view — non-owning 문자열 참조
  21. 21 absl::string_view 함정 — dangling·c_str·임시 객체
  22. 22 absl::StrCat — 가변 인자 문자열 연결과 AlphaNum
  23. 23 absl::StrSplit — Delimiter·Predicate·컨테이너 변환
  24. 24 absl::StrJoin — 컨테이너 결합과 Formatter
  25. 25 absl::StrFormat — type-safe printf·FormatSpec
  26. 26 Abseil ASCII 함수 — locale-free 분류·대소문자 변환
  27. 27 Abseil Escape — CEscape·HexEscape·Base64
  28. 28 absl::flat_hash_map — Swiss Table 기반 hash map
  29. 29 absl::flat_hash_set — set 버전 Swiss Table
  30. 30 absl::node_hash_map — stable pointer가 필요할 때
  31. 31 absl::btree_map — sorted·cache-friendly B-tree
  32. 32 absl::FixedArray — 런타임 크기 stack 배열
  33. 33 absl::InlinedVector — small buffer optimization
  34. 34 Abseil Swiss Table internals — control byte·SIMD probing
  35. 35 absl::Mutex — reader-writer·fairness·deadlock 검출
  36. 36 absl::Mutex Conditional Critical Section — Await로 cv 없애기
  37. 37 absl::Notification — once-only signal
  38. 38 absl::BlockingCounter·Barrier — 다중 thread 조율
  39. 39 absl::Mutex annotations — clang thread-safety로 race를 컴파일 타임에
  40. 40 absl::Time·Duration 분석 — 단단한 type
  41. 41 absl::Time Format·Parse
  42. 42 absl::CivilTime 분석
  43. 43 absl::time_zone 분석
  44. 44 absl::Time mocking — 테스트 친화 시간
  45. 45 absl::BitGen — 모던 난수 생성기
  46. 46 Abseil Random Distributions — Uniform·Exponential
  47. 47 Abseil Mocking Random — 테스트 결정성
  48. 48 Abseil Random Seeding·Entropy
  49. 49 absl::int128·uint128 분석
  50. 50 absl::bits — popcount·countl_zero
  51. 51 absl::optional vs std::optional
  52. 52 absl::variant 분석
  53. 53 absl::span 분석
  54. 54 absl::any 분석
  55. 55 absl::compare — three-way 비교
  56. 56 Abseil utility — apply·in_place
  57. 57 Abseil AbslHashValue 분석
  58. 58 Abseil HashState chaining
  59. 59 Abseil Custom hashable 구현
  60. 60 Abseil LOG·VLOG·CHECK 분석
  61. 61 Abseil LogSink 분석
  62. 62 Abseil LogEntry·structured logging
  63. 63 Abseil Stack trace·failure_signal_handler
  64. 64 ABSL_FLAG 정의 분석
  65. 65 Abseil ParseCommandLine 동작
  66. 66 Abseil Flag introspection·validation
  67. 67 Google 스타일의 Abseil 사용 패턴
  68. 68 Abseil 자주 보는 anti-pattern
  69. 69 std → absl 마이그레이션 전략
  70. 70 absl::Cleanup — 함수 종료 시 실행 보장
  71. 71 Abseil algorithm container 확장 — c_sort·c_find_if·c_count_if
  72. 72 absl::function_ref와 any_invocable — 함수 객체 전달의 두 축
  73. 73 absl::bind_front와 Overload — 함수 객체 보조 도구
  74. 74 absl::Cord — 분산 시스템용 대용량 문자열
  75. 75 absl::from_chars·SimpleAtoi — 빠른 숫자 변환
  76. 76 absl::Cord vs std::string — 선택 기준과 메모리 프로파일
  77. 77 absl::GetStackTrace와 Symbolize — crash 시 readable stack
  78. 78 absl::ComputeCrc32c — 하드웨어 가속 체크섬
  79. 79 absl::PeriodicSampler — 적응형 샘플링·jitter 회피