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

absl::Mutex Conditional Critical Section — Await로 cv 없애기

· Hawk · 4분 읽기

#한 줄 요약

absl::Mutex::Await(Condition)은 condition variable + notify + predicate while loop이라는 3단 관용구를 한 줄로 압축한다. condition을 predicate 함수로 표현하면, mutex가 unlock될 때마다 abseil이 자동 재평가하므로 producer가 notify를 호출할 필요가 없다.

#표준 condition variable의 함정

std::mutex mu;
std::condition_variable cv;
std::deque<Task> q;
// producer
void Push(Task t) {
{
std::lock_guard<std::mutex> l(mu);
q.push_back(std::move(t));
}
cv.notify_one(); // ① 누락하면 consumer 영원히 대기
}
// consumer
Task Pop() {
std::unique_lock<std::mutex> l(mu);
cv.wait(l, [&] { return !q.empty(); }); // ② predicate while 의무
Task t = std::move(q.front()); q.pop_front();
return t;
}

함정 셋:

  • notify 누락: producer가 wake 호출을 잊으면 consumer가 영원히 대기.
  • predicate while loop: spurious wakeup 때문에 if가 아니라 while.
  • 추가: notify_one vs notify_all 선택의 미묘함. 잘못 고르면 thundering herd 또는 lost wakeup.

#Mutex::Await의 모델

abseil의 모델은 다르다.

// API
class Mutex {
public:
void Await(const Condition& cond);
bool AwaitWithTimeout(const Condition& cond, absl::Duration timeout);
bool AwaitWithDeadline(const Condition& cond, absl::Time deadline);
bool LockWhen(const Condition& cond);
bool LockWhenWithTimeout(const Condition& cond, absl::Duration timeout);
};

Await(cond)cond가 true가 될 때까지 대기한다. 내부적으로 mutex가 unlock될 때마다 cond를 재평가한다. producer는 notify를 호출할 필요가 없다.

absl::Mutex mu;
std::deque<Task> q;
void Push(Task t) {
absl::MutexLock l(&mu);
q.push_back(std::move(t));
// notify 없음
}
Task Pop() {
absl::MutexLock l(&mu);
mu.Await(absl::Condition(+[](std::deque<Task>* q) {
return !q->empty();
}, &q));
Task t = std::move(q.front()); q.pop_front();
return t;
}

코드가 단순해진다. 빠뜨릴 곳이 줄어든다.

#Condition 객체

absl::Condition은 predicate 함수를 캡슐화한다. 세 가지 형태가 있다.

// 1) 멤버 함수 + this
class Server {
bool IsReady() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu_);
void Wait() {
absl::MutexLock l(&mu_);
mu_.Await(absl::Condition(this, &Server::IsReady));
}
};
// 2) bool* 직접
bool flag = false;
mu.Await(absl::Condition(&flag)); // *flag == true가 될 때까지
// 3) 함수 포인터 + 인자
mu.Await(absl::Condition(+[](Queue* q) { return !q->empty(); }, &queue));

predicate는 순수해야 한다. mutex가 잡힌 상태에서 자주 호출되므로 부수효과나 다른 lock 획득은 금지.

#LockWhen — lock과 condition 동시

Lock + Await을 합친 단축형.

mu.LockWhen(absl::Condition(&ready));
// ready가 true가 된 시점에 lock 보유
DoWork();
mu.Unlock();

MutexLockCondition을 받는 생성자로 조건이 참이 될 때까지 대기 후 잠그는 RAII도 제공한다.

absl::MutexLock l(&mu, absl::Condition(&ready));
DoWork();
// 자동 unlock

#Timeout / Deadline

absl::MutexLock l(&mu);
if (mu.AwaitWithTimeout(cond, absl::Seconds(5))) {
// 조건 만족
} else {
// timeout
}

absl::Duration, absl::Time 사용. 표준 std::chrono보다 표현력이 강하다 (Part 7에서 다룬다).

#내부 동작

Await은 wake-on-unlock 메커니즘이다. mutex가 unlock될 때마다 wait queue를 순회해 cond를 평가한다. true가 된 waiter는 wake.

// 의사 코드
void Mutex::Unlock() {
while (!wait_queue.empty()) {
Waiter* w = wait_queue.front();
if (w->cond.Eval()) { // cond 평가 (mu lock 보유)
w->Wake();
wait_queue.pop_front();
}
break; // 다음 waiter는 다음 unlock 때
}
ReleaseLock();
}

이 모델의 비용: producer가 wake할 의무는 없지만, unlock마다 cond 평가가 일어난다. cond가 비싸면 contention이 증가한다. 그래서 cond는 가벼워야 한다.

#std::condition_variable과의 비교

측면condition_variableMutex::Await
notify 호출producer 의무자동
spurious wakeupwhile loop 필수없음
predicate 위치호출 측 람다Condition 객체
코드 라인많음적음
성능wakeup 적게unlock마다 cond 평가

성능 트레이드오프가 있다. wake 패턴이 분명하고 predicate가 가벼우면 condition_variable이 약간 빠를 수 있다. 일반 코드는 Await의 명료함을 선택한다.

#코드 리뷰 포인트

1. cv.notify + cv.wait 패턴 → Await

// 회피
{
std::lock_guard l(mu);
ready = true;
}
cv.notify_one();
// ...
std::unique_lock l(mu);
cv.wait(l, [&] { return ready; });
// Good
{
absl::MutexLock l(&mu);
ready = true;
}
// ...
absl::MutexLock l(&mu);
mu.Await(absl::Condition(&ready));

2. predicate는 멤버 함수로

람다 + 캡처는 thread-safety 어노테이션이 잘 안 잡힌다. 멤버 함수 + ABSL_EXCLUSIVE_LOCKS_REQUIRED가 컴파일러 검증을 통과한다.

class Server {
bool ShouldStop() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu_);
void Run() {
absl::MutexLock l(&mu_);
while (!ShouldStop()) {
mu_.Await(absl::Condition(this, &Server::HasWork));
DoWork();
}
}
};

3. Condition 객체 재사용

같은 condition을 여러 번 쓰면 미리 만들어 둔다.

const absl::Condition has_work_(this, &Server::HasWork);
// ...
mu_.Await(has_work_);

#안티패턴

predicate에서 lock 획득

// 회피
mu.Await(absl::Condition(+[] {
other_mu.Lock(); // deadlock 위험
// ...
}));

predicate는 mutex가 잡힌 상태에서 호출된다. 다른 lock 획득은 lock 순서 위반을 부른다.

무거운 predicate

// 회피 — DB 쿼리, network 호출 등
mu.Await(absl::Condition(+[](DB* db) { return db->RowCount() > 0; }, &db));

unlock마다 평가되므로 contention 시 hot spot. 가벼운 메모리 검사로 한정.

Await 안에서 외부 상태 변경

predicate는 판정만 한다. 안에서 state mutation은 race 위험.

#정리

  • Mutex::Await(Condition)은 wait + notify + while loop을 한 줄로.
  • producer는 notify 의무가 없다.
  • Condition은 멤버 함수 / bool* / 함수 포인터 + 인자로 표현.
  • LockWhen / MutexLockWhen으로 lock-then-await 단축.
  • predicate는 가벼움, 순수, 다른 lock 없음.

#다음 편

Part 6-03 — Notification에서 한 번만 발생하는 signal에 특화된 primitive를 본다.

#관련 항목

Abseil Code Review · 36 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 회피