absl::Mutex annotations — clang thread-safety로 race를 컴파일 타임에
#한 줄 요약
ABSL_GUARDED_BY(mu_), ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu_), ABSL_LOCKS_EXCLUDED(mu_) 같은 매크로는 clang의 -Wthread-safety 정적 분석을 활성화한다. 멤버 변수와 함수에 lock 규약을 선언하면, 위반 시 컴파일 시점에 경고가 뜬다. race condition을 실행하기 전에 잡는 가장 효과적인 도구.
#동기
race condition은 보통 간헐적 버그다. 테스트가 안 잡고 production에서 데이터 손상으로 나타난다. 정적 분석으로 컴파일러가 누락된 lock을 잡으면 비용이 압도적으로 낮다.
clang은 -Wthread-safety 플래그로 lock 규약을 추적한다. abseil은 그 매크로를 portable한 형태로 제공한다.
전체 흐름을 그림으로 보면 다음과 같다.
#핵심 매크로
| 매크로 | 의미 | 부착 위치 |
|---|---|---|
ABSL_GUARDED_BY(mu) | 이 멤버는 mu 보유 상태에서만 접근 | 멤버 변수 |
ABSL_PT_GUARDED_BY(mu) | 포인터가 가리키는 객체가 mu 보호 | 포인터 멤버 |
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu) | 호출 측이 mu(exclusive) 보유해야 함 | 함수 |
ABSL_SHARED_LOCKS_REQUIRED(mu) | 호출 측이 mu(shared) 보유해야 함 | 함수 |
ABSL_LOCKS_EXCLUDED(mu) | 호출 시 mu를 보유하면 안 됨 (재진입 방지) | 함수 |
ABSL_ACQUIRED_BEFORE(mu) / ABSL_ACQUIRED_AFTER(mu) | lock 순서 선언 | mutex 멤버 |
ABSL_NO_THREAD_SAFETY_ANALYSIS | 분석 비활성화 (escape hatch) | 함수 |
#기본 사용
#include "absl/base/thread_annotations.h"#include "absl/synchronization/mutex.h"
class Cache { public: void Set(absl::string_view k, absl::string_view v) ABSL_LOCKS_EXCLUDED(mu_); std::string Get(absl::string_view k) const ABSL_LOCKS_EXCLUDED(mu_);
private: std::string LookupLocked(absl::string_view k) const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu_);
mutable absl::Mutex mu_; absl::flat_hash_map<std::string, std::string> data_ ABSL_GUARDED_BY(mu_);};
void Cache::Set(absl::string_view k, absl::string_view v) { absl::MutexLock l(&mu_); data_[std::string(k)] = std::string(v); // OK — mu_ 보유}
std::string Cache::Get(absl::string_view k) const { // 회피 — lock 없이 data_ 접근 // return data_[std::string(k)]; // 경고: writing data_ requires holding mu_
absl::MutexLock l(&mu_); return LookupLocked(k); // OK — LookupLocked는 mu_ 요구, 우리가 보유}
std::string Cache::LookupLocked(absl::string_view k) const { // mu_ 가 잡혀 있다고 가정 가능 auto it = data_.find(k); return it == data_.end() ? "" : it->second;}clang은 컴파일하며 다음을 검사한다.
data_접근 시mu_가 보유되어 있는가?LookupLocked호출 측이mu_를 보유하는가?Set이mu_를 잡지 않고 진입하는가? (LOCKS_EXCLUDED)
위반은 컴파일 경고.
#LOCKS_EXCLUDED — 재진입 방지
void Cache::Refresh() ABSL_LOCKS_EXCLUDED(mu_) { // 외부에서 mu_ 잡지 않고 호출되어야 함 absl::MutexLock l(&mu_); // ...}
void OtherMethod() { absl::MutexLock l(&mu_); Refresh(); // 경고 — mu_ 보유 상태에서 LOCKS_EXCLUDED 함수 호출}absl::Mutex는 비-재진입이다. 재진입 시 deadlock. annotation이 이를 정적으로 잡는다.
#SHARED vs EXCLUSIVE
class Config { public: std::string Get(absl::string_view k) const ABSL_SHARED_LOCKS_REQUIRED(mu_); // reader lock만으로 충분
void Set(absl::string_view k, absl::string_view v) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu_); // writer lock 필요
private: mutable absl::Mutex mu_; absl::flat_hash_map<std::string, std::string> data_ ABSL_GUARDED_BY(mu_);};
// 호출 측absl::ReaderMutexLock l(&cfg.mu_);cfg.Get(...); // OKcfg.Set(...); // 경고: EXCLUSIVE 요구#NO_THREAD_SAFETY_ANALYSIS — 마지막 수단
분석을 끌 수밖에 없는 경우가 있다.
- callback 안에서 lock 보유 가정이 정적으로 표현 안 될 때
- third-party API와의 경계
- thread-local 가정으로 안전한 코드
void TrickyHandler() ABSL_NO_THREAD_SAFETY_ANALYSIS { // 분석 비활성화}남발 금지. 주석으로 왜 안전한지 문서화.
#ACQUIRED_BEFORE / ACQUIRED_AFTER
여러 mutex의 lock 순서를 강제할 수 있다.
class Manager { absl::Mutex global_mu_; absl::Mutex local_mu_ ABSL_ACQUIRED_AFTER(global_mu_);};global_mu_ → local_mu_ 순서 강제. 역순으로 잡으면 경고.
#컴파일러 옵션
clang은 -Wthread-safety로 활성화. 보통 다음 플래그를 함께 쓴다.
clang++ -Wthread-safety -Wthread-safety-beta my_code.cc-Werror=thread-safety로 경고를 에러로 격상하면 빌드가 실패하므로 강제력이 생긴다.
GCC는 같은 분석을 지원하지 않는다. abseil 매크로는 GCC에서 no-op로 정의되어 컴파일은 성공한다.
#코드 리뷰 포인트
1. mutex가 있는 클래스는 모든 멤버에 GUARDED_BY
부분 적용은 잡을 수 있는 race를 놓친다. mutex와 함께 다니는 모든 데이터에 적용.
class Inbox { absl::Mutex mu_; std::deque<Msg> q_ ABSL_GUARDED_BY(mu_); size_t total_received_ ABSL_GUARDED_BY(mu_); // 빼먹지 말 것 size_t total_processed_ ABSL_GUARDED_BY(mu_);};2. private helper에 EXCLUSIVE_LOCKS_REQUIRED
class Manager { void DoWork(int x) ABSL_LOCKS_EXCLUDED(mu_); private: void DoWorkLocked(int x) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu_); absl::Mutex mu_;};DoWorkLocked 이름 + annotation의 결합이 가독성을 결정적으로 높인다.
3. mutex 잡고 callback 호출 검토
void Process() { absl::MutexLock l(&mu_); callback_(); // 회피 — callback이 무엇을 하는지 모름, 다른 lock 잡으면 deadlock}callback을 잡은 mutex 밖에서 호출하도록 재배치. annotation은 이를 직접 잡지 못하지만, 리뷰에서 의식적으로 살핀다.
4. NO_THREAD_SAFETY_ANALYSIS 사용 시 주석 의무
// Thread safety: caller guarantees mu_ is held via shared_ptr binding.void X() ABSL_NO_THREAD_SAFETY_ANALYSIS { ... }#안티패턴
경고 무시 빌드
-Wthread-safety를 켜고 경고가 누적되면 가치가 떨어진다. CI에서 -Werror=thread-safety로 강제.
임시 변수 escape
class Foo { public: Bar* GetUnsafe() ABSL_LOCKS_EXCLUDED(mu_) { absl::MutexLock l(&mu_); return &bar_; // 회피 — lock 밖으로 포인터 노출 } private: absl::Mutex mu_; Bar bar_ ABSL_GUARDED_BY(mu_);};annotation이 통과해도 런타임에는 unsafe. 포인터 노출 자체가 race를 만든다.
#정리
- abseil의 thread-safety 매크로 + clang
-Wthread-safety로 lock 규약을 컴파일 타임 검증. GUARDED_BY,EXCLUSIVE_LOCKS_REQUIRED,LOCKS_EXCLUDED가 핵심 셋.- mutex 있는 클래스의 모든 멤버에 일관 적용.
NO_THREAD_SAFETY_ANALYSIS는 마지막 수단, 항상 주석.- CI에서
-Werror=thread-safety로 강제력 확보.
#다음 편
Part 6이 끝났다. Part 7에서 시간/Duration/CivilTime을 다룬다.
#관련 항목
Abseil Code Review · 39 of 79
- 1 Abseil Code Review — Google production-grade C++ 라이브러리 분석
- 2 Abseil 개요 — Google이 std를 보완한 이유
- 3 Abseil 설계 철학 — std 호환과 추가 기능의 균형
- 4 Abseil 빌드와 의존성 — Bazel vs CMake
- 5 Abseil LTS vs HEAD 릴리스 모델 분석
- 6 Abseil Versioning과 ABI 호환성 정책
- 7 Abseil 매크로 — ABSL_HAVE_*·ABSL_ATTRIBUTE_*
- 8 Abseil ABSL_PREDICT_TRUE/FALSE — branch hint
- 9 absl::LogSeverity — 로그 레벨 타입
- 10 Abseil type_traits — negation·conjunction·void_t
- 11 Abseil Conformance·Policy 분석
- 12 Abseil Memory utilities 분석
- 13 Abseil raw_logging — heap-free 로깅
- 14 Abseil thread_annotations — clang TSA 통합
- 15 absl::Status — exception-free error handling
- 16 absl::StatusOr<T> — 값 또는 에러
- 17 absl status_macros — ASSIGN_OR_RETURN·RETURN_IF_ERROR
- 18 absl::Status payload — 구조화된 에러 컨텍스트
- 19 absl::Status ↔ exception 변환 패턴
- 20 absl::string_view — non-owning 문자열 참조
- 21 absl::string_view 함정 — dangling·c_str·임시 객체
- 22 absl::StrCat — 가변 인자 문자열 연결과 AlphaNum
- 23 absl::StrSplit — Delimiter·Predicate·컨테이너 변환
- 24 absl::StrJoin — 컨테이너 결합과 Formatter
- 25 absl::StrFormat — type-safe printf·FormatSpec
- 26 Abseil ASCII 함수 — locale-free 분류·대소문자 변환
- 27 Abseil Escape — CEscape·HexEscape·Base64
- 28 absl::flat_hash_map — Swiss Table 기반 hash map
- 29 absl::flat_hash_set — set 버전 Swiss Table
- 30 absl::node_hash_map — stable pointer가 필요할 때
- 31 absl::btree_map — sorted·cache-friendly B-tree
- 32 absl::FixedArray — 런타임 크기 stack 배열
- 33 absl::InlinedVector — small buffer optimization
- 34 Abseil Swiss Table internals — control byte·SIMD probing
- 35 absl::Mutex — reader-writer·fairness·deadlock 검출
- 36 absl::Mutex Conditional Critical Section — Await로 cv 없애기
- 37 absl::Notification — once-only signal
- 38 absl::BlockingCounter·Barrier — 다중 thread 조율
- 39 absl::Mutex annotations — clang thread-safety로 race를 컴파일 타임에
- 40 absl::Time·Duration 분석 — 단단한 type
- 41 absl::Time Format·Parse
- 42 absl::CivilTime 분석
- 43 absl::time_zone 분석
- 44 absl::Time mocking — 테스트 친화 시간
- 45 absl::BitGen — 모던 난수 생성기
- 46 Abseil Random Distributions — Uniform·Exponential
- 47 Abseil Mocking Random — 테스트 결정성
- 48 Abseil Random Seeding·Entropy
- 49 absl::int128·uint128 분석
- 50 absl::bits — popcount·countl_zero
- 51 absl::optional vs std::optional
- 52 absl::variant 분석
- 53 absl::span 분석
- 54 absl::any 분석
- 55 absl::compare — three-way 비교
- 56 Abseil utility — apply·in_place
- 57 Abseil AbslHashValue 분석
- 58 Abseil HashState chaining
- 59 Abseil Custom hashable 구현
- 60 Abseil LOG·VLOG·CHECK 분석
- 61 Abseil LogSink 분석
- 62 Abseil LogEntry·structured logging
- 63 Abseil Stack trace·failure_signal_handler
- 64 ABSL_FLAG 정의 분석
- 65 Abseil ParseCommandLine 동작
- 66 Abseil Flag introspection·validation
- 67 Google 스타일의 Abseil 사용 패턴
- 68 Abseil 자주 보는 anti-pattern
- 69 std → absl 마이그레이션 전략
- 70 absl::Cleanup — 함수 종료 시 실행 보장
- 71 Abseil algorithm container 확장 — c_sort·c_find_if·c_count_if
- 72 absl::function_ref와 any_invocable — 함수 객체 전달의 두 축
- 73 absl::bind_front와 Overload — 함수 객체 보조 도구
- 74 absl::Cord — 분산 시스템용 대용량 문자열
- 75 absl::from_chars·SimpleAtoi — 빠른 숫자 변환
- 76 absl::Cord vs std::string — 선택 기준과 메모리 프로파일
- 77 absl::GetStackTrace와 Symbolize — crash 시 readable stack
- 78 absl::ComputeCrc32c — 하드웨어 가속 체크섬
- 79 absl::PeriodicSampler — 적응형 샘플링·jitter 회피
관련 글
Abseil thread_annotations — clang TSA 통합
Part 2-08: ABSL_GUARDED_BY, ABSL_LOCKS_EXCLUDED — clang의 thread safety analysis로 mutex 사용을 컴파일 시점에 검증.
같은 시리즈에서 이어 읽기
absl::BlockingCounter·Barrier — 다중 thread 조율
Part 6-04: absl::BlockingCounter와 absl::Barrier — N개 작업 완료 대기, N개 thread 동기 합류, fanout-fanin 패턴.
같은 시리즈에서 이어 읽기
absl::Notification — once-only signal
Part 6-03: absl::Notification — 한 번만 발생하는 이벤트를 위한 가벼운 signal primitive. atomic flag보다 안전, condition variable보다 단순.
같은 시리즈에서 이어 읽기
이 글을 참조하는 글 (5)
- Abseil 자주 보는 anti-pattern — Abseil Code Review
- absl::BlockingCounter·Barrier — 다중 thread 조율 — Abseil Code Review
- absl::Mutex Conditional Critical Section — Await로 cv 없애기 — Abseil Code Review
- absl::Mutex — reader-writer·fairness·deadlock 검출 — Abseil Code Review
- Abseil thread_annotations — clang TSA 통합 — Abseil Code Review