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

folly::CPUThreadPoolExecutor — CPU-bound 작업의 표준 thread pool

· Hawk · 4분 읽기

한 줄 요약: CPUThreadPoolExecutor는 fixed-size thread pool로 CPU-bound 작업을 실행한다. priority/queue/factory가 모두 교체 가능한 라이브러리급 구현이다.

#동기 — 표준에 thread pool이 없다

C++ 표준에는 thread pool이 없다. std::async(std::launch::async)가 매번 새 thread를 만들 수도 안 만들 수도 있다는 점이 모호하고, std::thread는 직접 manage해야 한다.

Folly는 production thread pool을 정확히 두 모양으로 제공한다.

  • CPUThreadPoolExecutor — CPU 집약 작업 (parse, compute, serialize)
  • IOThreadPoolExecutor — I/O 작업 (libevent EventBase 기반)

두 pool의 분리는 blocking 작업이 CPU 작업을 starve하지 못하게 한다.

#기본 사용

#include <folly/executors/CPUThreadPoolExecutor.h>
folly::CPUThreadPoolExecutor pool(8); // 8 threads
pool.add([] {
doExpensiveWork();
});
// Future와 통합
computeSemi()
.via(&pool)
.thenValue([](int x) { return process(x); });

constructor의 첫 인자는 thread 개수다. 일반적으로 std::thread::hardware_concurrency() 또는 그 절반/배수를 쓴다.

#구조

// folly/executors/CPUThreadPoolExecutor.h (요약)
class CPUThreadPoolExecutor : public ThreadPoolExecutor {
public:
CPUThreadPoolExecutor(
size_t numThreads,
std::unique_ptr<BlockingQueue<CPUTask>> taskQueue =
std::make_unique<UnboundedBlockingQueue<CPUTask>>(),
std::shared_ptr<ThreadFactory> threadFactory =
std::make_shared<NamedThreadFactory>("CPUThreadPool"));
void add(Func) override;
void add(Func, std::chrono::milliseconds timeout, Func expireCallback);
void addWithPriority(Func, int8_t priority) override;
size_t numActiveThreads() const;
size_t numPendingTasks() const;
};

세 부분이 조합 가능하다.

  1. BlockingQueue — 어떤 큐 정책 (unbounded / bounded / priority)
  2. ThreadFactory — thread 생성/이름/affinity
  3. CPUTask — task 자체

#모델 위치

CPUThreadPoolExecutor는 Fixed thread pool 모델 — 공유 큐 + N 워커.

Executor models compared

평행성을 얻고 스레드 수를 bounded로 유지하는 균형점이다. 단일 큐가 hot이면 work-stealing(또는 priority queue로 분리)으로 contention을 분산할 수 있다.

#Queue 선택

// 무제한 — 메모리 폭발 가능
auto q1 = std::make_unique<folly::UnboundedBlockingQueue<CPUTask>>();
// 제한 — 가득 차면 add()가 block
auto q2 = std::make_unique<folly::LifoSemMPMCQueue<CPUTask>>(1000);
// priority queue
auto q3 = std::make_unique<folly::PriorityLifoSemMPMCQueue<CPUTask>>(
3, // 3 priority levels
1000); // capacity per level
folly::CPUThreadPoolExecutor pool(8, std::move(q3));
pool.addWithPriority([] { ... }, folly::Executor::HI_PRI);
pool.addWithPriority([] { ... }, folly::Executor::LO_PRI);

priority queue는 latency-sensitive taskbackground batch를 분리할 때 유용하다.

#ThreadFactory

auto factory = std::make_shared<folly::NamedThreadFactory>("Parser");
folly::CPUThreadPoolExecutor pool(4, factory);
// 생성된 thread는 "Parser-0", "Parser-1", ... 이름을 가짐

top -H -p <pid> 또는 perf trace에서 thread 이름이 보이면 디버깅이 한층 쉬워진다. 모든 thread pool에 명시적 이름을 주는 게 권장이다.

// 사용자 정의 factory — CPU affinity
class AffinityThreadFactory : public folly::ThreadFactory {
public:
std::thread newThread(folly::Func&& f) override {
return std::thread([f = std::move(f)]() mutable {
cpu_set_t set;
CPU_ZERO(&set);
CPU_SET(specificCore_, &set);
pthread_setaffinity_np(pthread_self(), sizeof(set), &set);
f();
});
}
};

#Task — work + metadata

folly/executors/CPUThreadPoolExecutor.h
struct CPUTask {
Func func;
std::chrono::steady_clock::time_point enqueueTime;
std::chrono::milliseconds expireTime{0};
Func expireCallback;
int8_t priority{Executor::MID_PRI};
};

enqueueTimequeue에서 대기한 시간을 측정한다. tail latency 분석에 핵심이다.

pool.add(
[] { doWork(); }, // task
std::chrono::milliseconds(100), // 100ms 내에 시작 안 되면 expire
[] { LOG(WARN) << "task expired"; }); // expireCallback

#stop / join 정책

class ThreadPoolExecutor {
public:
void stop(); // 즉시 신규 task 거절, in-flight task는 진행
void join(); // 모든 in-flight 끝날 때까지 wait
void setNumThreads(size_t); // dynamic resize
};
// destructor가 자동 join
{
folly::CPUThreadPoolExecutor pool(8);
pool.add(...);
} // ← 여기서 join (in-flight task 끝날 때까지)

destructor가 모든 task가 끝날 때까지 block한다. 이를 모르면 원치 않게 block되는 경우가 있다.

#std::thread / std::async와 비교

// std::thread — 직접 manage
std::vector<std::thread> threads;
for (int i = 0; i < N; ++i) {
threads.emplace_back([] { work(); });
}
for (auto& t : threads) t.join();
// std::async — launch policy 모호
auto f = std::async(std::launch::async, [] { return work(); });
int v = f.get(); // thread 생성/파괴 비용
// folly::CPUThreadPoolExecutor — pool reuse
static folly::CPUThreadPoolExecutor pool(8);
pool.add([] { work(); }); // thread 재사용

std::async매번 thread를 만들 수 있다(implementation defined). pool은 fixed thread를 재사용한다.

#InlineExecutor와의 차이

항목InlineExecutorCPUThreadPoolExecutor
실행caller threadpool worker thread
비동기성없음있음
메모리0thread당 stack + queue
사용 사례test, 단축production

#코드 리뷰 포인트

  • thread 수가 hardcoded인가? std::thread::hardware_concurrency() 또는 config로 받자.
  • queue가 unbounded인가? burst 트래픽에서 메모리 폭발 가능. bounded + expire 정책 고려.
  • thread 이름이 적절한가? “Pool-0”보다 “Parser-0”, “Encoder-0”이 디버깅에 좋다.
  • 여러 pool이 있는가? I/O와 CPU 분리, latency-sensitive와 batch 분리.

#자주 보는 안티패턴

// 1. 매 함수 호출마다 새 pool 생성
void handle() {
folly::CPUThreadPoolExecutor pool(4); // thread 생성/파괴 — 매우 비쌈
pool.add([] { work(); });
} // destructor가 join
// 2. unbounded queue + slow consumer
folly::CPUThreadPoolExecutor pool(2); // 2 thread
for (int i = 0; i < 1'000'000; ++i) {
pool.add([] { sleep(1); }); // 큐가 무한정 자람
}
// 3. pool 안에서 .get() 호출
pool.add([&] {
auto v = anotherSemi().via(&pool).get(); // deadlock 위험
// pool worker 가 자기 자신을 wait
});
// 4. CPU pool에서 blocking I/O
pool.add([] {
read(fd, buf, n); // CPU thread 가 I/O로 block — pool starve
});

(3)은 pool 크기가 작을 때 모든 worker가 서로를 wait해 deadlock한다. CPU pool에서 같은 CPU pool에 schedule된 결과를 wait하지 마라.

#정리

  • CPUThreadPoolExecutor는 fixed-size pool로 CPU 집약 작업을 실행한다.
  • Queue/ThreadFactory/Task가 모두 교체 가능한 모듈식 구조다.
  • priority queue로 latency-sensitive와 batch를 분리한다.
  • thread 이름을 명시해 디버깅 가능성을 높인다.
  • destructor가 join하므로 원치 않는 block에 주의한다.
  • I/O 작업은 별도의 IOThreadPoolExecutor로 분리한다.

#다음 편

Part 3-03: IOThreadPoolExecutor에서 libevent 기반 I/O pool을 본다.

#관련 항목

Folly Code Review · 15 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 사용의 실전