본문으로 건너뛰기
Folly Code Review · 42/89

folly::Baton — one-shot wait 동기화

· Hawk · 4분 읽기

#한 줄 요약

folly::Baton<>한 번 post → 한 번 wait의 one-shot 동기화 primitive. mutex와 condition_variable 없이 4-byte 상태로 thread간 signal을 전달한다. Future/Promise의 핵심 building block.

#동기

다음 같은 패턴은 condition_variable로 흔히 작성한다.

std::mutex mu;
std::condition_variable cv;
bool ready = false;
// thread A
void wait_for_ready() {
std::unique_lock g(mu);
cv.wait(g, []{ return ready; });
}
// thread B
void signal_ready() {
{ std::lock_guard g(mu); ready = true; }
cv.notify_all();
}

mutex + bool + cv 세 개를 들고 다녀야 한다. 의도는 signal 한 번인데 코드는 그것보다 일반적인 도구.

Baton은 이 패턴 전용. 한 번 발사하는 사격 신호.

folly::Baton<> b;
// thread A
b.wait(); // post 될 때까지 sleep
// thread B
b.post(); // wake

#API & 사용법

#include <folly/synchronization/Baton.h>
// 1. 기본 — single post, multiple-wait OK (n번째 wait도 즉시 통과)
folly::Baton<> b;
b.wait(); // post 전까지 block
b.post(); // wake all (보통 하나)
// 2. 타임아웃
if (b.try_wait_for(std::chrono::seconds(1))) {
// posted
} else {
// timeout
}
// 3. try_wait — non-blocking
if (b.try_wait()) { /* 이미 posted */ }
// 4. 재사용 — reset
b.reset();
// 그 후 wait/post 다시 가능
// 5. blocking mode template param
folly::Baton<true> spin_baton; // spin 한 후 futex (기본)
folly::Baton<false> futex_only; // spin 없이 바로 sleep

folly::Baton은 default가 spin-then-futex hybrid. 짧은 wait는 spin, 길면 sleep.

#내부 구현

// 약식 — folly/synchronization/Baton.h
template <bool MayBlock = true>
class Baton {
std::atomic<uint32_t> state_;
// state values:
// INIT = 0
// WAITING = 1
// EARLY_DELIVERY = 2 (post가 wait 전에 옴)
// TIMED_OUT = 3
};

4-byte. 추가 mutex/cv 없음.

#post

// 약식
void post() {
uint32_t prev = state_.exchange(EARLY_DELIVERY,
std::memory_order_release);
if (prev == WAITING) {
// 누가 sleep 중
futex_wake_one(&state_);
}
}

CAS 없이 exchange 한 번. WAITING이면 wake.

#wait

// 약식
void wait() {
// 1. spin 시도
for (int spin = 0; spin < kMaxSpins; ++spin) {
uint32_t s = state_.load(std::memory_order_acquire);
if (s == EARLY_DELIVERY) return;
cpu_pause();
}
// 2. WAITING으로 표시 후 futex sleep
uint32_t expected = INIT;
if (state_.compare_exchange_strong(expected, WAITING,
std::memory_order_acquire)) {
while (state_.load() == WAITING) {
futex_wait(&state_, WAITING);
}
}
// 그 외 EARLY_DELIVERY 였음 — 즉시 반환
}

spin → CAS WAITING → futex_wait. linux의 futex(FUTEX_WAIT)가 state가 더 이상 WAITING이 아니면 즉시 반환.

#왜 condition_variable보다 빠른가

cv는 보통 mutex.lock() → predicate 확인 → wait → predicate 확인 사이클. Baton은:

Baton:
state load (acquire) → spin → CAS → futex_wait
state exchange (release) → futex_wake
cv:
mutex.lock → predicate → mutex.unlock → futex_wait
mutex.lock → state change → mutex.unlock → futex_wake_all → cv_mu.lock

cv는 mutex 두 개 (사용자 mutex + cv 내부)를 잡았다 놓는다. Baton은 0개. 따라서 cv보다 3-5배 빠른 시그널.

#release/acquire가 보장하는 것

Baton의 state.store(POSTED, release) + state.load(acquire)가 mutex 없이 publish-subscribe를 안전하게 만드는 이유.

Happens-before via release/acquire

release store 이전의 모든 쓰기가 acquire load 이후의 모든 읽기에 visible. poster가 채워 놓은 데이터를 waiter가 그대로 읽을 수 있다. 이게 lock-free 자료구조의 토대다.

#사용 사례

#1. Future/Promise 내부

// 약식 — folly::Future가 내부적으로
struct State {
folly::Baton<> baton;
std::optional<T> value;
};
void Promise::setValue(T v) {
state_->value = std::move(v);
state_->baton.post();
}
T Future::get() {
state_->baton.wait();
return std::move(*state_->value);
}

Future가 wait/post의 깊은 곳에 Baton.

#2. Worker thread startup signal

folly::Baton<> ready;
std::thread worker([&]() {
Setup();
ready.post(); // ready
RunLoop();
});
ready.wait(); // setup 끝까지 대기
StartUsingWorker();

#3. Test에서 lifecycle 동기

TEST(MyTest, AsyncCallback) {
folly::Baton<> done;
service.DoAsync([&]() { done.post(); });
ASSERT_TRUE(done.try_wait_for(std::chrono::seconds(5)));
}

cv보다 코드가 짧다.

#std/abseil 비교

// std — cv 풀세트
std::condition_variable cv;
std::mutex mu;
bool flag = false;
void wait() { std::unique_lock g(mu); cv.wait(g, []{ return flag; }); }
// abseil — Notification
absl::Notification n;
n.WaitForNotification();
n.Notify(); // 한 번만 가능
// folly
folly::Baton<> b;
b.wait();
b.post(); // 여러 번 호출 가능, 추가 post는 no-op
항목std::cv + mutexabsl::Notificationfolly::Baton
Use casegeneralone-shotone-shot
sizeof80+ byte~16 byte4 byte
Spurious wakeup가능없음없음
ResetN/AXO
Multiple postN/A한 번만 (assert)무해

abseil Notification이 가장 가깝다. 둘 다 한 번 signal 의미. folly가 약간 더 작고 reset 가능.

#코드 리뷰 포인트

// Bad — Baton을 반복 사용 (reset 없이)
folly::Baton<> b;
for (int i = 0; i < N; ++i) {
worker.Submit([&]{ ... b.post(); });
b.wait();
// 두 번째부터는 EARLY_DELIVERY 라 즉시 통과 → race!
}
// Good — 매번 reset
for (int i = 0; i < N; ++i) {
b.reset();
worker.Submit([&]{ ... b.post(); });
b.wait();
}

post는 idempotent하나 wait는 state를 한번만 본다. 반복은 reset 필수.

// Good — timeout으로 deadlock 검출
if (!b.try_wait_for(std::chrono::seconds(30))) {
LOG(ERROR) << "deadlock?";
}

production에서 영원 wait는 위험. 합리적 timeout.

// 주의 — Baton이 stack object일 때 lifetime
{
folly::Baton<> b;
worker.Submit([&b]{ b.post(); });
b.wait();
} // worker가 아직 b를 본다면? — wait 후 destruct이므로 OK

wait 반환 = post가 이미 일어남. wait return 후 baton destruct 안전.

#안티패턴

  • Baton을 cv 대체로 일반 signal에 사용: Baton은 한 번 의미. 반복 신호는 cv 또는 semaphore.
  • post 후 reset 없이 wait 재호출: 즉시 통과 → 동기화 실패. reset 필수.
  • wait 안에서 다른 lock 잡기: Baton wait는 spin → futex로 lock-free path. 다른 lock 잡으면 inversion 위험. wait는 단순히 signal 대기로만.

#정리

  • folly::Baton<>은 4-byte one-shot signal primitive.
  • spin → futex hybrid로 짧은 wait는 spin, 길면 sleep.
  • cv보다 3-5배 빠름. Future/Promise 내부의 building block.
  • try_wait_for로 timeout, reset()으로 재사용.
  • 반복 신호는 cv 또는 semaphore.

#다음 편

RWSpinLock은 매우 짧은 critical section 용 spin-only RW lock. SharedMutex보다 가벼우나 longer wait에 부적합.

#관련 항목

Folly Code Review · 43 of 89

  1. 1 Folly Code Review — Meta의 production-grade C++ 라이브러리 코드 분석
  2. 2 Folly 개요 — Meta가 production에서 검증한 utility 모음 분석
  3. 3 Folly vs Abseil 철학 비교 — performance-first vs std-compatible
  4. 4 Folly 빌드와 fbcode 환경 — monorepo의 그림자
  5. 5 Folly API stability 정책 — 어떤 보장도 없다는 솔직함
  6. 6 Folly production validation 문화 — peta-scale에서 단련된 코드
  7. 7 folly::Future 분석 — std::future의 한계를 넘는 composable async
  8. 8 folly::Promise·makeFuture — Future를 만드는 두 길
  9. 9 folly::SemiFuture vs Future — executor binding의 명시화
  10. 10 folly::Future thenValue·thenError·thenTry — continuation 체인 분석
  11. 11 folly::collect·collectAll·collectAny — fan-in 패턴 분석
  12. 12 folly::Future retry·window·via — 제어 흐름 조합자
  13. 13 folly::fibers 분석 — M:N stackful coroutine
  14. 14 folly::InlineExecutor — 호출자 thread에서 즉시 실행
  15. 15 folly::CPUThreadPoolExecutor — CPU-bound 작업의 표준 thread pool
  16. 16 folly::IOThreadPoolExecutor — libevent 기반 I/O pool
  17. 17 folly::ManualExecutor — 결정적 테스트를 위한 수동 진행
  18. 18 folly::EventBase 분석 — libevent 이벤트 루프의 핵심
  19. 19 folly::IOBuf 분석 — zero-copy buffer chain의 기본 단위
  20. 20 folly::IOBufQueue — chain의 push/pull 추상화
  21. 21 folly::io::Cursor·RWCursor — chain 위의 stream
  22. 22 folly Zero-copy 패턴 — IOBuf로 ScatterGather I/O 표현
  23. 23 folly::IOBuf shared semantics — clone·unshare·takeOwnership
  24. 24 folly::FBString 분석 — SSO + COW 구현
  25. 25 folly의 fmt::format 통합 — 모던 포맷팅 채택
  26. 26 folly::StringPiece — string_view 호환 분석
  27. 27 folly Join·Split utilities — 문자열 분해와 결합
  28. 28 folly::to·tryTo — text↔num 변환 분석
  29. 29 folly Conv Customization — 사용자 타입 지원
  30. 30 folly Conv 성능 비교 — sprintf·stringstream 대비
  31. 31 folly::F14ValueMap vs std::unordered_map
  32. 32 folly::F14NodeMap — stable pointer가 필요할 때
  33. 33 folly::F14VectorMap — cache-friendly iteration
  34. 34 folly::F14FastMap — auto-select 동작
  35. 35 folly F14 internals — SIMD probing 메커니즘
  36. 36 folly::small_vector — inline storage 분석
  37. 37 folly::FixedString — compile-time string
  38. 38 folly::AtomicHashMap — lock-free read 분석
  39. 39 folly::ConcurrentHashMap — sharded 동시 해시 맵
  40. 40 folly::EvictingCacheMap — LRU 구현 분석
  41. 41 folly::Synchronized — lock wrapper 패턴
  42. 42 folly::SharedMutex 분석
  43. 43 folly::Baton — one-shot wait 동기화
  44. 44 folly::RWSpinLock 분석
  45. 45 folly::PicoSpinLock — 1-byte spinlock
  46. 46 folly::ProducerConsumerQueue — SPSC 큐 분석
  47. 47 folly::MPMCQueue — multi-producer multi-consumer
  48. 48 folly::UnboundedQueue — 동적 크기 lock-free
  49. 49 folly::fibers::Channel — Go-like channel
  50. 50 folly::dynamic — JSON-like dynamic type 분석
  51. 51 folly JSON conversion — toJson·parseJson
  52. 52 folly dynamic ↔ struct — manual marshaling
  53. 53 folly dynamic Visitor pattern — type별 분기
  54. 54 folly::Singleton vs Meyers/static — 왜 Folly의 Singleton인가
  55. 55 folly::SingletonVault 분석 — 등록·소멸·의존성
  56. 56 folly::Singleton try_get·try_get_fast — TLS-cached 접근
  57. 57 folly::ExceptionWrapper — type-erased exception holder
  58. 58 folly::ScopeGuard·SCOPE_EXIT — RAII cleanup
  59. 59 folly::Optional vs std::optional
  60. 60 folly::Function vs std::function
  61. 61 folly::Lazy — 지연 초기화 wrapper
  62. 62 folly Meta 스타일 code review 패턴
  63. 63 folly anti-patterns — 잘못 쓰면 std보다 느림
  64. 64 folly vs std 선택 기준 분석
  65. 65 folly::coro 개요 — production C++20 코루틴 어댑터
  66. 66 folly::coro::Task — lazy single-shot 코루틴
  67. 67 folly::coro::AsyncGenerator — 비동기 스트림
  68. 68 folly coro blockingWait·collectAll — 동기 경계와 fan-in
  69. 69 folly::coro::Baton·Mutex — 코루틴-aware 동기화
  70. 70 folly::Expected — 결과 또는 오류
  71. 71 folly::Try — Future 결과 wrapper
  72. 72 folly::Try vs Expected 선택 기준
  73. 73 folly::Range — 일반 iterator pair
  74. 74 folly::Uri — URL 파서
  75. 75 folly Fingerprint64·128 — 분산 hash
  76. 76 folly SpookyHashV2 — fast non-crypto hash
  77. 77 folly::Init — main() 부트스트랩
  78. 78 folly::Indestructible — global lifetime 패턴
  79. 79 folly::MicroLock — 1-byte 락
  80. 80 folly::MicroSpinLock — 가장 좁은 spin lock
  81. 81 folly::format — legacy formatter 분석
  82. 82 folly::demangle — typeid 디망글링
  83. 83 folly::DynamicConverter — dynamic ↔ struct
  84. 84 folly::RecordIO — append-only 로그 파일 포맷
  85. 85 folly::io::Compression — zstd·lz4·snappy wrapper
  86. 86 folly::AsyncIO — io_uring·Linux AIO
  87. 87 folly::CancellationToken — 코루틴·Future 취소 전파
  88. 88 folly::observer — hot config의 atomic refresh
  89. 89 fbcode 패턴 모음 — folly 사용의 실전