Concurrency in Modern C++
c3-3 built a race condition with raw pthreads. This chapter shows modern C++'s own equivalents — genuinely nicer to use, but not a different safety story.
std::thread
A higher-level replacement for pthread_create/pthread_join — but with a real, important gotcha: a std::thread still joinable when its destructor runs calls std::terminate. It does not automatically join or detach the way an RAII-styled course might suggest; .join() or .detach() must be called explicitly before destruction.
A Worked Example — Revisiting c3-3's Race Condition
The exact same race — the higher-level wrapper changes nothing about the underlying problem, only the syntax used to create the threads.
std::mutex and std::lock_guard
std::mutex is the same concept as c3-3's own pthread_mutex_t. std::lock_guard<std::mutex> is a genuine RAII wrapper around locking — the direct fix to c3-3's own "forgotten unlock deadlocks" warning. Its destructor unlocks automatically, on any exit path, including an exception — exactly cpp2-6's own exception-safety mechanism, applied specifically to locking.
std::atomic
A genuinely different, lower-overhead alternative for simple shared counters and flags specifically.
Resolves c3-3's own race condition with no lock/unlock pairing whatsoever — a real, meaningful alternative worth knowing for exactly this narrow case.
Contrasted With Rust's Send and Sync, Revisited
std::mutex/std::lock_guard are a genuine ergonomic improvement over c3-3's raw pthread_mutex_t — automatic unlocking via RAII, no more forgotten-unlock deadlocks. But the same fundamental gap from c3-3 remains: nothing in C++'s type system ties a std::mutex to the specific data it protects, and nothing prevents accessing that data without holding the lock at all. Rust's Mutex<T> — wrapping the data itself, checked by the Send/Sync marker traits at compile time — is still the genuinely stronger guarantee. lock_guard is a real ergonomic win, not a safety win at the type-system level.
| Concept | pthreads (c3-3) | Modern C++ | Rust |
|---|---|---|---|
| Thread creation | pthread_create/join | std::thread, .join()/.detach() | std::thread::spawn |
| Locking | manual lock/unlock, forgettable | std::lock_guard — RAII, automatic unlock | Mutex<T> wraps the data itself |
| Mutex-to-data relationship | none — separate variables | still none — separate variables | enforced by the type system |
| Accessing data without locking | compiles, UB at runtime | compiles, UB at runtime | not possible — no name refers to the data directly |
std::atomic for simple counters/flags; std::lock_guard (or std::unique_lock for more flexibility) for anything needing genuine mutex protection. Avoid raw lock()/unlock() pairs in new code entirely.
lock_guard, std::thread does not solve this via RAII — a thread still joinable when destroyed calls std::terminate immediately. Genuinely easy to forget on an early return or exception path, echoing cpp2-6's own exception-safety themes, but not automatically handled here the way locking is.
Coding Challenges
Rewrite this chapter's own counter race using std::thread instead of pthreads, run it a few times, and report whether the final value is consistently 200,000.
📄 View solutionFix Challenge 1 two different ways: once using std::lock_guard around the increment, and once using std::atomic
Explain precisely why std::lock_guard is described as a real ergonomic improvement over c3-3's raw pthread_mutex_t but NOT a safety improvement at the type-system level, tying your answer to what Rust's Mutex<T> does differently.
📄 View solutionChapter 4 Quick Reference
std::thread— must be explicitly.join()ed or.detach()ed, or its destructor callsstd::terminatestd::lock_guard<std::mutex>— RAII locking, unlocks automatically on any exit path including exceptionsstd::atomic<T>— lock-free, for simple counters/flags specifically- Modern C++'s tools are a real ergonomic improvement over raw pthreads — not a type-system safety improvement
- The mutex-to-data gap from
c3-3remains — Rust'sMutex<T>still closes it, C++'s doesn't - Next chapter: undefined behavior in C++ — building on
c3-4's own C-level UB catalog