folly::coro::Baton·Mutex — 코루틴-aware 동기화
한 줄 요약:
std::mutex는 thread를 block한다. 코루틴 안에서 그러면 executor의 thread 한 칸을 통째로 잠근다.folly::coro::Mutex/Baton은 코루틴만 suspend해 같은 thread가 다른 일을 한다.
#동기
코루틴의 핵심 자원은 thread가 아니라 frame이다. thread는 executor pool에 소수만 있다(보통 CPU 코어 수). 코루틴 frame은 수십만 개 살 수 있다. 그런데 코루틴 안에서 std::mutex::lock()을 쓰면 thread가 block된다. frame은 살아있는데 thread가 없는 상황.
// Badfolly::coro::Task<void> Process(SharedData& d) { std::lock_guard lk(d.mu); // ← thread block. pool 1/n 잠금. co_await asyncWrite(d.snapshot()); d.update();}이 코드는 co_await 동안 thread가 자유로워지긴 한다. 그러나 lock_guard가 살아있는 동안 mutex를 점유하므로 다른 코루틴이 lock()을 시도하면 thread가 block된다. executor 4-thread pool에서 4개 코루틴이 lock 경합하면 모든 thread가 idle하지 못한다.
coro::Mutex는 lock 경합 시 코루틴만 suspend, thread는 다른 코루틴을 실행한다.
#coro::Baton
#include <folly/coro/Baton.h>
folly::coro::Baton baton;
folly::coro::Task<void> Waiter() { co_await baton; // suspend until post // do work}
folly::coro::Task<void> Poster() { // ... compute ... baton.post(); // wake all waiters}Baton은 1-shot edge-triggered notification. post()가 한 번 호출되면 그 이후의 co_await은 즉시 통과. reset()으로 재사용 가능.
multi-waiter 가능:
folly::coro::Baton ready;
auto t1 = []() -> folly::coro::Task<void> { co_await ready; /* ... */ }();auto t2 = []() -> folly::coro::Task<void> { co_await ready; /* ... */ }();auto t3 = []() -> folly::coro::Task<void> { co_await ready; /* ... */ }();
// ... 시간이 흐른 후ready.post(); // 셋 다 동시에 깨어남여러 코루틴이 같은 baton을 기다릴 수 있다. post() 시 모두 깨운다.
#coro::Mutex
#include <folly/coro/Mutex.h>
folly::coro::Mutex mu;int counter = 0;
folly::coro::Task<void> Increment() { auto lock = co_await mu.co_scoped_lock(); // critical section — 다른 코루틴이 이 mu에 락 시도하면 suspend co_await asyncWork(); ++counter; // lock 소멸자가 unlock}co_scoped_lock()은 awaitable. await 결과로 RAII lock guard 반환. critical section에서 다른 co_await도 가능하다 — await 도중에도 lock은 유지된다.
이게 std::mutex와 결정적으로 다른 점.
// std::mutex와 std::async — lock 들고 async 안 됨std::mutex m;auto fut = std::async(std::launch::async, [&] { std::lock_guard lk(m); return otherAsync().get(); // block — thread 다시 잠김});coro::Mutex는 코루틴 frame에 lock 상태가 매여 있어 suspend 동안 thread가 다른 코루틴을 돌릴 수 있다.
#coro::SharedMutex
folly::coro::SharedMutex mu;Config cfg;
folly::coro::Task<Config> Read() { auto lk = co_await mu.co_scoped_lock_shared(); co_return cfg;}
folly::coro::Task<void> Write(Config c) { auto lk = co_await mu.co_scoped_lock(); cfg = std::move(c);}reader/writer 분리. read가 많은 hot path에서 유리.
#coro::Semaphore
folly::coro::Semaphore sem{4}; // 최대 4 동시
folly::coro::Task<void> BatchProcess(std::vector<Item> items) { std::vector<folly::coro::Task<void>> tasks; for (auto& item : items) { tasks.push_back([&, item]() -> folly::coro::Task<void> { auto guard = co_await sem.co_scoped_lock(); co_await Process(item); }()); } co_await folly::coro::collectAllRange(std::move(tasks));}동시 처리 수를 제한. backpressure pattern. 외부 API rate limit, DB connection pool 같은 자리에 자연스럽다.
#내부 구조 — wait list
// folly/coro/Mutex.h 약식class Mutex { public: auto co_lock() noexcept { struct Awaiter { Mutex* mu; std::coroutine_handle<> handle_; Awaiter* next_ = nullptr;
bool await_ready() noexcept { return mu->tryLock(); } void await_suspend(std::coroutine_handle<> h) noexcept { handle_ = h; mu->addWaiter(this); // atomic linked list 앞에 push } void await_resume() noexcept {} }; return Awaiter{this}; }
void unlock() noexcept { auto* w = popWaiter(); // atomic pop if (w) { // resume on executor — current thread 또는 captured executor executor_->add([h = w->handle_] { h.resume(); }); } else { locked_.store(false); } }};waiter들이 atomic linked list에 줄을 선다. unlock()이 한 명을 깨워 그의 코루틴을 executor에 schedule한다. 곧바로 그 자리에서 resume하면 stack이 깊어지므로 executor를 거치는 게 정석.
#std::mutex와의 비교
| 항목 | std::mutex | coro::Mutex |
|---|---|---|
| 대기 시 | thread block | 코루틴 suspend |
| 재진입 | non-recursive | non-recursive |
| 조건 변수 | std::condition_variable 별도 | Baton/Channel 별도 |
| critical section 안 async | block 가능 (위험) | 자연스럽게 OK |
| sizeof | ~40 byte (glibc) | 보통 더 작음 (waiter list head만) |
std::mutex를 코루틴 안에서 짧은 동기 critical section에만 쓰는 건 OK. 그러나 critical section 안에서 co_await이 등장하면 무조건 coro::Mutex.
#fiber와의 관계
folly::fibers::Baton이 fibers 도메인의 짝이다.
| 도메인 | 동기화 | 어디서 suspend |
|---|---|---|
| std thread | std::mutex / condition_variable | OS scheduler |
| folly::fibers | fibers::Baton / fibers::TimedMutex | fiber scheduler |
| folly::coro | coro::Mutex / coro::Baton | executor |
세 시스템이 비슷한 API로 추상화를 통일하려는 흐름이 보인다.
#코드 리뷰 포인트
- 코루틴 안 critical section에
co_await이 있는데std::mutex사용 → 즉시coro::Mutex로 교체 대상. Baton을 multi-shot으로 쓰려고 함 →reset()이 필요하거나Channel고려.- semaphore 슬롯이 lock guard 없이
acquire/release분리 → 예외 시 leak.co_scoped_lock사용. - mutex가 hot path에 있고 read가 압도적이면 SharedMutex 검토.
#자주 보는 안티패턴
// 1. coro::Mutex 사용하지만 std::lock_guard와 혼용folly::coro::Mutex mu;{ std::lock_guard lk(mu); // 컴파일 에러 또는 wrong overload co_await ...;}
// 2. Baton을 broadcast로 사용한 뒤 reset 잊음baton.post();// 다음 라운드 시작co_await baton; // 즉시 통과 — 의도와 다름
// 3. Semaphore acquire 후 await 도중 throw, release 안 됨co_await sem.acquire(); // raw acquireco_await mayThrow(); // 예외 → semaphore 영원히 점유co_await sem.release();// → co_scoped_lock으로 RAII 처리해야#정리
- 코루틴 안 동기화는 thread를 block하지 않는 코루틴-aware primitive가 필요하다.
coro::Baton은 1-shot notification, multi-waiter 가능.coro::Mutex/SharedMutex/Semaphore는 std와 표층 API가 비슷하지만 suspend 의미론.- critical section 안에서
co_await이 등장한다면 무조건 coro variant. - waiter는 atomic linked list로 줄을 서고 unlock 시 executor를 거쳐 resume.
#다음 편
Part 16에서 Expected/Try — error 표현의 두 갈래를 다룬다.
#관련 항목
Folly Code Review · 69 of 89
- 1 Folly Code Review — Meta의 production-grade C++ 라이브러리 코드 분석
- 2 Folly 개요 — Meta가 production에서 검증한 utility 모음 분석
- 3 Folly vs Abseil 철학 비교 — performance-first vs std-compatible
- 4 Folly 빌드와 fbcode 환경 — monorepo의 그림자
- 5 Folly API stability 정책 — 어떤 보장도 없다는 솔직함
- 6 Folly production validation 문화 — peta-scale에서 단련된 코드
- 7 folly::Future 분석 — std::future의 한계를 넘는 composable async
- 8 folly::Promise·makeFuture — Future를 만드는 두 길
- 9 folly::SemiFuture vs Future — executor binding의 명시화
- 10 folly::Future thenValue·thenError·thenTry — continuation 체인 분석
- 11 folly::collect·collectAll·collectAny — fan-in 패턴 분석
- 12 folly::Future retry·window·via — 제어 흐름 조합자
- 13 folly::fibers 분석 — M:N stackful coroutine
- 14 folly::InlineExecutor — 호출자 thread에서 즉시 실행
- 15 folly::CPUThreadPoolExecutor — CPU-bound 작업의 표준 thread pool
- 16 folly::IOThreadPoolExecutor — libevent 기반 I/O pool
- 17 folly::ManualExecutor — 결정적 테스트를 위한 수동 진행
- 18 folly::EventBase 분석 — libevent 이벤트 루프의 핵심
- 19 folly::IOBuf 분석 — zero-copy buffer chain의 기본 단위
- 20 folly::IOBufQueue — chain의 push/pull 추상화
- 21 folly::io::Cursor·RWCursor — chain 위의 stream
- 22 folly Zero-copy 패턴 — IOBuf로 ScatterGather I/O 표현
- 23 folly::IOBuf shared semantics — clone·unshare·takeOwnership
- 24 folly::FBString 분석 — SSO + COW 구현
- 25 folly의 fmt::format 통합 — 모던 포맷팅 채택
- 26 folly::StringPiece — string_view 호환 분석
- 27 folly Join·Split utilities — 문자열 분해와 결합
- 28 folly::to·tryTo — text↔num 변환 분석
- 29 folly Conv Customization — 사용자 타입 지원
- 30 folly Conv 성능 비교 — sprintf·stringstream 대비
- 31 folly::F14ValueMap vs std::unordered_map
- 32 folly::F14NodeMap — stable pointer가 필요할 때
- 33 folly::F14VectorMap — cache-friendly iteration
- 34 folly::F14FastMap — auto-select 동작
- 35 folly F14 internals — SIMD probing 메커니즘
- 36 folly::small_vector — inline storage 분석
- 37 folly::FixedString — compile-time string
- 38 folly::AtomicHashMap — lock-free read 분석
- 39 folly::ConcurrentHashMap — sharded 동시 해시 맵
- 40 folly::EvictingCacheMap — LRU 구현 분석
- 41 folly::Synchronized — lock wrapper 패턴
- 42 folly::SharedMutex 분석
- 43 folly::Baton — one-shot wait 동기화
- 44 folly::RWSpinLock 분석
- 45 folly::PicoSpinLock — 1-byte spinlock
- 46 folly::ProducerConsumerQueue — SPSC 큐 분석
- 47 folly::MPMCQueue — multi-producer multi-consumer
- 48 folly::UnboundedQueue — 동적 크기 lock-free
- 49 folly::fibers::Channel — Go-like channel
- 50 folly::dynamic — JSON-like dynamic type 분석
- 51 folly JSON conversion — toJson·parseJson
- 52 folly dynamic ↔ struct — manual marshaling
- 53 folly dynamic Visitor pattern — type별 분기
- 54 folly::Singleton vs Meyers/static — 왜 Folly의 Singleton인가
- 55 folly::SingletonVault 분석 — 등록·소멸·의존성
- 56 folly::Singleton try_get·try_get_fast — TLS-cached 접근
- 57 folly::ExceptionWrapper — type-erased exception holder
- 58 folly::ScopeGuard·SCOPE_EXIT — RAII cleanup
- 59 folly::Optional vs std::optional
- 60 folly::Function vs std::function
- 61 folly::Lazy — 지연 초기화 wrapper
- 62 folly Meta 스타일 code review 패턴
- 63 folly anti-patterns — 잘못 쓰면 std보다 느림
- 64 folly vs std 선택 기준 분석
- 65 folly::coro 개요 — production C++20 코루틴 어댑터
- 66 folly::coro::Task — lazy single-shot 코루틴
- 67 folly::coro::AsyncGenerator — 비동기 스트림
- 68 folly coro blockingWait·collectAll — 동기 경계와 fan-in
- 69 folly::coro::Baton·Mutex — 코루틴-aware 동기화
- 70 folly::Expected — 결과 또는 오류
- 71 folly::Try — Future 결과 wrapper
- 72 folly::Try vs Expected 선택 기준
- 73 folly::Range — 일반 iterator pair
- 74 folly::Uri — URL 파서
- 75 folly Fingerprint64·128 — 분산 hash
- 76 folly SpookyHashV2 — fast non-crypto hash
- 77 folly::Init — main() 부트스트랩
- 78 folly::Indestructible — global lifetime 패턴
- 79 folly::MicroLock — 1-byte 락
- 80 folly::MicroSpinLock — 가장 좁은 spin lock
- 81 folly::format — legacy formatter 분석
- 82 folly::demangle — typeid 디망글링
- 83 folly::DynamicConverter — dynamic ↔ struct
- 84 folly::RecordIO — append-only 로그 파일 포맷
- 85 folly::io::Compression — zstd·lz4·snappy wrapper
- 86 folly::AsyncIO — io_uring·Linux AIO
- 87 folly::CancellationToken — 코루틴·Future 취소 전파
- 88 folly::observer — hot config의 atomic refresh
- 89 fbcode 패턴 모음 — folly 사용의 실전
관련 글
folly::CancellationToken — 코루틴·Future 취소 전파
CancellationSource/Token의 전파 모델 — coroutine·Future·callback 트리에서 협력적 취소.
같은 시리즈에서 이어 읽기
folly coro blockingWait·collectAll — 동기 경계와 fan-in
blockingWait, collectAll, collectAllRange — sync 경계 연결과 병렬 합성, deadlock 회피 규칙.
같은 시리즈에서 이어 읽기
folly::coro::AsyncGenerator — 비동기 스트림
AsyncGenerator<T>의 pull-based 모델, co_yield, for co_await — 비동기 iterator의 표준 후보 패턴.
같은 시리즈에서 이어 읽기