04_Concurrency — Multithreading and concurrency, C++17 and Win32, to production depth¶
What this course covers¶
You already run threads in production. Your application server creates Win32 threads, guards
shared tables with CRITICAL_SECTION, waits on Events and Mutex handles, and serves loan-origination
requests through a pooled request model. What you have not done yet is any of that in C++, and you
have never had to explain why it works: what a data race really is, what the compiler and the CPU
are allowed to reorder, what "acquire" and "release" mean, or how to prove a thread-pool shutdown
cannot lose a task.
This course takes you from "I use locks and it has not crashed" to "I can design, explain and debug a concurrent server, in C++17 on Linux and in C on Win32, and I can defend every decision in a senior interview."
Every Win32 primitive you use today is mapped to its C++17 standard-library and POSIX equivalent, so you can describe your existing work in portable terms and write the same designs in modern C++.
Why it is on the resume¶
Three resume items depend on it directly:
- "multithreaded application server handling transactional loan-origination requests"
- "thread synchronisation layer — mutexes, critical sections, wait objects and a pooled request model"
- tcpkv: "fixed thread pool ... store guarded by shared_mutex"
and two more depend on it indirectly: "in-memory caching" (a cache shared by many threads is a concurrency problem first and a data-structure problem second) and "concurrency faults" (which you cannot debug without a model of what the hardware is doing).
Interviewers for senior C++ roles ask about this topic in nearly every loop. The questions are predictable: data races, mutex vs atomic, condition-variable predicate loops, memory orders, deadlock diagnosis, thread-pool design, false sharing. This course covers every one of them to the depth where you can derive the answer rather than recite it.
Prerequisites¶
- C at a professional level (you have this).
- C++ basics: classes, RAII destructors, templates at the "I can read
std::vector<T>" level, lambdas. If[&](int x) { return x + n; }looks foreign, do the first modules of 02_Modern_CPP first and come back. - WSL Ubuntu 24.04 with
g++ --versionreporting GCC 13. Every C++ file incode/compiles with:
- For ThreadSanitizer (modules 02 and 12): add
-fsanitize=thread -g -O1. - Win32
.cexamples are Windows-only. Build them with MSVC (cl /W4 file.c) or MinGW GCC (gcc -Wall file.c). They are reference material for module 13, not the main track.
Modules¶
| # | File | Topic | Hours |
|---|---|---|---|
| 01 | 01_processes_threads_and_the_os.md |
What a thread is, kernel view, pthread vs Win32 vs std::thread, create/join/detach, TLS, how many threads | 2.5 |
| 02 | 02_data_races_and_why_they_are_ub.md |
Data race definition, undefined behaviour, torn reads, compiler and CPU reordering, TSan intro | 2.5 |
| 03 | 03_mutexes_lock_guard_unique_lock.md |
std::mutex, lock_guard, unique_lock, scoped_lock, recursive and timed mutexes, CRITICAL_SECTION mapping, futex | 3 |
| 04 | 04_shared_mutex_and_readers_writers.md |
shared_mutex, shared_lock, reader-writer trade-offs, writer starvation, the tcpkv store | 2 |
| 05 | 05_condition_variables_and_wait_objects.md |
condition_variable, predicate loops, spurious and lost wakeups, Win32 Events, producer-consumer queue | 3 |
| 06 | 06_deadlocks_livelocks_starvation.md |
Four conditions, lock ordering, hierarchies, try_lock backoff, deadlocks in crash dumps, priority inversion | 2.5 |
| 07 | 07_atomics_and_the_memory_model.md |
std::atomic, CAS, memory orders, happens-before, fences, volatile, x86 vs ARM | 4 |
| 08 | 08_lock_free_basics.md |
Spinlock, SPSC ring buffer, Treiber stack and ABA, hazard pointers, when not to go lock-free | 3 |
| 09 | 09_thread_pools_and_task_queues.md |
Fixed thread pool from scratch, futures and promises, std::async pitfalls, shutdown, exceptions, backpressure | 4 |
| 10 | 10_cache_lines_false_sharing_performance.md |
MESI, false sharing, alignas(64), per-thread counters, Amdahl, steady-state benchmarking | 2.5 |
| 11 | 11_concurrency_patterns_for_servers.md |
Thread-per-connection vs pool vs event loop, actors, RCU snapshots, call_once, connection pools | 3 |
| 12 | 12_debugging_concurrency_bugs.md |
TSan, helgrind, thread-id logging, stress and sleep injection, all-thread stacks in gdb and WinDbg, bug catalogue | 3 |
| 13 | 13_win32_concurrency_reference.md |
CreateThread, CRITICAL_SECTION, SRWLOCK, CONDITION_VARIABLE, Events, Semaphores, WFMO, Interlocked, thread pool API, IOCP; side-by-side table | 2 |
Total: about 37 hours. At 4 to 5 hours a week alongside other courses, this is 8 weeks. Modules 01 to 05 are the core; do them first and in order. 07 is the hardest and the most asked about in interviews; budget a second pass for it. 13 is a reference you can read in one sitting and return to.
Supporting files¶
code/— 20 complete programs. Each module names the files it uses.EXERCISES.md— every exercise from every module, with difficulty and a hint.INTERVIEW_QA.md— 36 questions from basic to senior, with model answers.
Suggested order with the other courses¶
- Do 02_Modern_CPP modules on RAII and lambdas before module 03 here.
- Module 11 previews epoll and the event loop; the full treatment is in 08_Networking.
- Module 12's crash-dump reading pairs with the debugging course's WinDbg material.
You are done when you can...¶
- State precisely what a data race is (two accesses, at least one write, not atomic, not ordered by happens-before) and explain why the standard makes it undefined behaviour rather than "unspecified value".
- Write a producer-consumer queue with
std::mutexandstd::condition_variable, and explain from first principles why the wait must be in a predicate loop. - Explain acquire and release in the "message passing" model, and say what seq_cst adds that acquire/release does not.
- Write a fixed-size thread pool whose shutdown provably drains every submitted task, and whose tasks can throw without killing a worker.
- Look at the stacks of eight blocked threads in gdb or WinDbg and name the two that are deadlocked and the lock order that caused it.
- Explain why
std::shared_mutexcan be slower thanstd::mutexand when that happens. - Explain false sharing with a drawing of two cache lines and say what
alignas(64)fixes. - Describe your Win32 synchronisation layer entirely in portable terms: CRITICAL_SECTION is a non-recursive-in-spirit mutex with a spin phase, SRWLOCK is a shared_mutex, an auto-reset Event is roughly a binary semaphore, WaitForMultipleObjects has no direct std equivalent.
- Design the locking for a master-table cache that is read by 200 threads and refreshed every five minutes, using an immutable snapshot and one atomic pointer swap.
- Explain the ABA problem and why a Treiber stack needs tagged pointers or hazard pointers.
Resume lines this course backs up¶
- "multithreading & concurrency"
- "multithreaded application server handling transactional loan-origination requests"
- "thread synchronisation layer — mutexes, critical sections, wait objects and a pooled request model"
- "in-memory caching"
- tcpkv: "fixed thread pool ... store guarded by shared_mutex"
- "concurrency faults"