Rung 5 (M7) — Concurrency Duo: Thread Pool + SPSC Ring Buffer¶
Nav: ← Rung 4 · Rung 6 → · Source: Phase 3 · P3.1/P3.2
This is the first rare artifact on the ladder. Most C++ portfolios do not contain any lock-free code. If yours does — and it passes ThreadSanitizer, and has honest benchmarks against Boost.Lockfree, and includes latency histograms — you are now visibly in a different candidate tier from the average applicant.
Rung 5 is a single repo with two subprojects. Both live under one roof because they solve the same problem (“communicate between threads efficiently”) at two different scales: work distribution (thread pool) vs. streaming data handoff (SPSC ring buffer). Bundling them lets one repo tell a coherent concurrency story.
What It Is¶
A public GitHub repo named cpp-concurrency-kit with two subdirs:
thread_pool/¶
A modern C++20 thread pool:
thread_poolclass withsubmit(F&& f, Args&&...) -> std::future<...>.Uses
std::jthreadandstd::stop_token(C++20) for cooperative shutdown.Configurable worker count (default:
std::thread::hardware_concurrency()).Optional priority variant:
priority_thread_poolwith a bounded number of priority levels.Optional work-stealing variant:
work_stealing_thread_pool(this is the stretch — do only if base ships).Unit tests + stress tests + TSan CI job.
spsc_ring/¶
A single-producer single-consumer lock-free ring buffer:
spsc_ring<T, Capacity>.Capacity must be a power of two (documented and enforced at compile time via
static_assert).Uses
std::atomic<size_t>with explicit memory ordering (acquire/release, notseq_cst). Justify the choices in a comment above each atomic op — this is where reviewers will look hardest.Cache-line padding to prevent false sharing between head and tail (use
alignas(std::hardware_destructive_interference_size)with a fallback constant if unavailable).try_push(T&&) -> boolandtry_pop(T&) -> bool. No blocking variants.Unit tests + stress tests + TSan CI job.
Repo-level¶
Top-level README with the 5-section base plus a Latency Histograms section rendering three charts:
Thread pool: task submission-to-completion latency at concurrency 1, 4, 16 workers.
SPSC ring: producer-to-consumer latency at message sizes 8, 64, 512, 4096 bytes.
SPSC ring: throughput vs. Boost.Lockfree’s
spsc_queueat the same message sizes.
Benchmarks live in
benchmarks/and use google/benchmark.CI runs: build, test, ASan, UBSan, and TSan on every push. TSan is the point.
Why It Matters (Employer Signal)¶
One line: “Writes and validates lock-free code — rare outside HFT and systems teams.”
Lock-free correctness under ThreadSanitizer is a hard skill to fake. The signal from this rung is not just “knows concurrency” — it is “has actually finished a concurrent artifact and validated it.” That is the difference between reading Anthony Williams’s book and having shipped the code from it. For applied-C++/ML roles at ByteDance, F5, GM Cruise, and every inference-engineering team, this rung answers the question they will ask: “can this person write the runtime, not just the model?”
Acceptance Checklist¶
Public GitHub repo named
cpp-concurrency-kit, MIT license.Two subdirs:
thread_pool/andspsc_ring/, each independently buildable.Top-level
CMakeLists.txtbuilds both.All tests pass under ThreadSanitizer. This is non-negotiable. If TSan reports a race, the rung is not done.
All tests also pass under ASan and UBSan.
Thread pool: submit + get result works for
void,int, and move-only return types.Thread pool: destructor drains outstanding tasks before returning (documented behavior).
Thread pool: stress test submits 1M no-op tasks across 8 workers; completes with no leaks under ASan.
SPSC ring: stress test with a producer and consumer thread each doing 10M ops; no data loss, no ordering violations, TSan-clean.
SPSC ring: every atomic operation has a comment explaining its memory ordering.
Benchmark chart PNGs committed under
docs/and rendered in README.Benchmark comparison against
boost::lockfree::spsc_queuedocumented honestly (win, tie, or lose — report the truth).CI matrix: {GCC 13, Clang 17} × {Release, ASan, UBSan, TSan}. All green.
Repo shared on r/cpp with
[Show r/cpp]tag.
README Structure Additions¶
On top of the 5-section base:
Design notes — One paragraph per subproject. For the thread pool: why
std::jthread, how shutdown works. For the SPSC ring: why power-of-two capacity, what memory ordering you chose and why, how you avoid false sharing.Latency Histograms — The three charts described above. This is the part that gets clicks.
Correctness Testing — A section explicitly listing: TSan runs clean, ASan runs clean, UBSan runs clean, stress test iterations completed. This is the credibility section.
Not Suitable For — Say plainly: this SPSC ring is not MPMC. Do not use in production without understanding this. Honesty here is respected.
Common Ways This Rung Fails¶
TSan reports a race and you ignore it. Do not ship until it is clean. A TSan race in a lock-free artifact means the artifact is wrong, not “probably fine.” Even if the tests pass, the code is broken.
You use
std::memory_order_seq_csteverywhere. Correct but slow, and reviewers on r/cpp will call it out. Useacquire/releasewhere you can justify it. If you can’t justify,seq_cstis fine — but say so in the comment.You forget cache-line padding on the head/tail atomics. Then producer and consumer contend on the same cache line and your benchmark is 5-10× slower than it should be.
You benchmark under
-O0or withTSanenabled. Benchmarks must be-O3 -DNDEBUGand without sanitizers. Separate CI job for TSan; separate for perf. Document the flags.You claim to beat Boost.Lockfree without checking. Almost certainly false at message sizes > 64 bytes. Post the numbers honestly. Reviewers respect “we lose by 15% at 4KB messages but win at 8 bytes” way more than “we beat Boost.”
You build a work-stealing pool as your base scope and don’t finish. Stick to the simple bounded-queue design for the base. Work-stealing is the stretch.
What Most People Get Wrong¶
They get the code working and never validate it. Lock-free code that “seems to work” is where careers go to die. The value of this rung is not the code — it is the evidence of validation: TSan output, stress-test iteration counts, latency histograms. If your repo doesn’t render those, a reviewer has no way to distinguish it from the hundreds of “my thread pool” repos on GitHub. The validation IS the artifact.
Second common mistake: shipping only the thread pool because the SPSC ring is harder. Then the rung’s rare-skill signal collapses — thread pools are common. The SPSC ring is what makes this rung a differentiator. If you have to cut, cut the work-stealing stretch, not the ring buffer.
Extension Challenges (Rank-Ordered)¶
Work-stealing thread pool — highest signal. If you have two weeks of slack, do this. Reference: Chase-Lev deque.
MPMC ring buffer — significantly harder than SPSC. Requires per-slot sequence numbers (Vyukov MPMC design). Do only if concurrency is genuinely your thing.
Coroutine-based
co_await-able thread pool — C++20 coroutines, natural fit. This is a wow-factor stretch.perf-generated flamegraph committed todocs/and rendered in README. Zero code, high signal.Formal reasoning notes on the memory-ordering choices, cross-referenced against Herb Sutter’s atomic weapons talk. This is nerd-cred.
Links to Source Phase Files¶
Engineering plan:
../04_phase_3_systems_and_concurrency/— P3.1 and P3.2 specs.Memory model reading: same folder, look for the “C++ memory model” doc and the linked cppreference chapters.
If TSan output confuses you:
../11_tools_setup/— sanitizer setup guide.If you fall behind:
../13_discipline/and../99_pre_mortem/.
Nav: ← Rung 4 · Rung 6 → · Source: Phase 3 · P3.1/P3.2