absl::BlockingCounter·Barrier — 다중 thread 조율
#한 줄 요약
BlockingCounter는 N개 작업이 모두 완료될 때까지 대기한다 (fanout-fanin). Barrier는 N개 thread가 모두 도착하면 모두 동시에 진행시킨다 (rendezvous). 둘 다 한 번만 동작하며, parallel coordination의 두 주요 패턴을 직접 표현한다.
#동기
병렬 작업 분배 후 합류는 흔한 패턴이다.
- map-reduce의 map 단계 완료 대기
- 여러 worker가 결과를 모은 후 다음 단계 진입
- 테스트에서 여러 thread가 같은 시점에 시작하도록 동기화
표준 도구 조합으로 구현 가능하지만 코드가 늘어진다. BlockingCounter/Barrier는 의미를 명시한다.
#BlockingCounter — fanout-fanin
#include "absl/synchronization/blocking_counter.h"
void DoWork(absl::BlockingCounter* counter, int i) { // ... counter->DecrementCount();}
void Run() { constexpr int kN = 10; absl::BlockingCounter counter(kN);
for (int i = 0; i < kN; ++i) { std::thread([&counter, i] { DoWork(&counter, i); }).detach(); }
counter.Wait(); // 모든 worker가 DecrementCount할 때까지}흐름:
BlockingCounter(N)으로 카운터 초기화.- N개 worker가 각자 일 후
DecrementCount(). - main이
Wait(). 카운터가 0이 되면 반환.
#API
namespace absl {class BlockingCounter { public: explicit BlockingCounter(int initial_count); bool DecrementCount(); // counter==0이 되면 true 반환 void Wait();};
class Barrier { public: explicit Barrier(int num_threads); bool Block(); // 마지막 도착 thread가 true, 나머지는 false};}두 API 모두 의도가 분명하다.
#Barrier — rendezvous
#include "absl/synchronization/barrier.h"
void Worker(absl::Barrier* barrier, int id) { Prepare(id); if (barrier->Block()) { // 마지막 도착자 — cleanup 책임 LOG(INFO) << "all workers arrived"; } // 여기서부터 모두 동시에 진행 Process(id);}
void Run() { constexpr int kN = 8; absl::Barrier b(kN); std::vector<std::thread> threads; for (int i = 0; i < kN; ++i) { threads.emplace_back([&b, i] { Worker(&b, i); }); } for (auto& t : threads) t.join();}Block()은 N개 thread가 모두 도착할 때까지 막는다. 마지막 도착자는 true를 반환 — 정확히 한 thread가 cleanup/logging 책임을 진다는 의미.
Barrier는 자기 자신을 소멸시킨다. 마지막 thread가 Block()을 떠난 후 barrier 객체는 더 이상 유효하지 않다. 반복 사용 X.
#BlockingCounter vs Notification vs Barrier
| primitive | 신호자 수 | 대기자 수 | 사용 |
|---|---|---|---|
Notification | 1 | 다수 | 1회 이벤트 알림 |
BlockingCounter | N | 1 (또는 다수) | N개 작업 완료 대기 |
Barrier | N | N | N thread 동시 진행 |
이 표를 머리에 두고 워크로드 형태에 맞춰 고르면 된다.
#내부 구현
BlockingCounter는 mutex + counter다.
// absl/synchronization/blocking_counter.cc (요약)class BlockingCounter { absl::Mutex lock_; int count_; int num_waiting_; bool done_;};
void BlockingCounter::Wait() { absl::MutexLock l(&lock_); lock_.Await(absl::Condition(&done_));}
bool BlockingCounter::DecrementCount() { absl::MutexLock l(&lock_); if (--count_ <= 0) done_ = true; return done_;}Await로 자동 wake. notify 의무 없음. Barrier도 비슷한 구조 — counter + flag로 모두 도착 검출.
#std::latch / std::barrier (C++20) 비교
C++20에 동일 목적의 표준이 들어왔다.
| std (C++20) | absl |
|---|---|
std::latch | BlockingCounter |
std::barrier | Barrier (재사용 가능 X, C++ std는 가능) |
std::barrier는 reusable barrier(generation 개념)지만 absl::Barrier는 1회용. abseil이 LTS 호환을 위해 더 보수적이다.
C++20을 쓸 수 있다면 std::latch/std::barrier가 선택지에 들어온다. 다만 같은 코드베이스 안에서 동기화 도구를 일관시키기 위해 abseil을 유지하는 게 흔하다.
#코드 리뷰 포인트
1. atomic counter + wait loop → BlockingCounter
// 회피 — busy waitstd::atomic<int> remaining{N};for (int i = 0; i < N; ++i) { std::thread([&] { Work(); remaining.fetch_sub(1); }).detach();}while (remaining.load() > 0) std::this_thread::yield();
// Goodabsl::BlockingCounter counter(N);for (int i = 0; i < N; ++i) { std::thread([&counter] { Work(); counter.DecrementCount(); }).detach();}counter.Wait();2. 테스트에서 동시 시작
TEST(Race, RareCondition) { constexpr int kN = 16; absl::Barrier start(kN); std::vector<std::thread> threads; for (int i = 0; i < kN; ++i) { threads.emplace_back([&] { start.Block(); // 모두 도착할 때까지 대기 — 정확히 같은 시점에 진행 DoRaceyOp(); }); } for (auto& t : threads) t.join();}race condition 재현 테스트의 표준 패턴.
3. fanout 후 결과 수집
std::vector<Result> results(N);absl::BlockingCounter c(N);for (int i = 0; i < N; ++i) { pool->Schedule([&, i] { results[i] = ComputeShard(i); c.DecrementCount(); });}c.Wait();Result merged = Merge(results);#안티패턴
Barrier 재사용
// 회피 — Barrier는 1회용absl::Barrier b(N);for (round : rounds) { // ... b.Block(); // 첫 라운드 OK, 두 번째부터 UB}반복 동기화는 새 Barrier 또는 std::barrier(C++20) 사용.
DecrementCount 누락
// 회피 — worker가 예외로 die하면 counter는 N에서 멈춤 → Wait 영원std::thread([&] { Work(); counter.DecrementCount(); }).detach();Work이 throw 가능하면 RAII로 보호.
std::thread([&] { absl::Cleanup dec = [&counter] { counter.DecrementCount(); }; Work(); // throw 해도 dec가 호출됨}).detach();(absl::Cleanup은 Part 14 또는 별도 글 참조.)
count 0으로 초기화
absl::BlockingCounter c(0);c.Wait(); // 즉시 반환 — 의미 없음쓸모 없는 코드. linter가 잡지 않으니 리뷰에서.
#정리
BlockingCounter: N개 완료 대기 (fanout-fanin).Barrier: N thread 동시 진행 (rendezvous), 1회용.Notification: 1회 이벤트 (전편).- C++20
std::latch/std::barrier로 대체 가능. - DecrementCount 누락 방지를 위해
absl::CleanupRAII.
#다음 편
Part 6-05 — Mutex annotations에서 clang -Wthread-safety로 lock 누락을 컴파일 타임에 잡는 방법을 본다.
#관련 항목
Abseil Code Review · 38 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 회피
관련 글
absl::Mutex annotations — clang thread-safety로 race를 컴파일 타임에
Part 6-05: ABSL_GUARDED_BY, ABSL_LOCKS_EXCLUDED, ABSL_EXCLUSIVE_LOCKS_REQUIRED — clang -Wthread-safety와 결합해 lock 누락을 정적 검출.
같은 시리즈에서 이어 읽기
absl::Notification — once-only signal
Part 6-03: absl::Notification — 한 번만 발생하는 이벤트를 위한 가벼운 signal primitive. atomic flag보다 안전, condition variable보다 단순.
같은 시리즈에서 이어 읽기
absl::Mutex Conditional Critical Section — Await로 cv 없애기
Part 6-02: Mutex::Await로 함수형 condition을 정의해 condition_variable, notify, spurious wakeup 관용구를 모두 제거.
같은 시리즈에서 이어 읽기