You did POSIX locks at Cisco, so the concepts here are familiar — this is the C++ standard-library expression of them. Concurrency is central at trading firms: market data on one thread, order management on another, all touching shared state. Getting it wrong causes the worst kind of bug: the one that only appears under load.
Threads
#include <thread>
void work(int id) { std::cout << "thread " << id << "\n"; }
std::thread t1(work, 1); // starts running immediately
std::thread t2(work, 2);
t1.join(); // wait for t1 to finish
t2.join(); // wait for t2 — you MUST join (or detach) every threadIf a std::thread is destroyed while still joinable (neither joined nor detached), the program calls std::terminate and dies. This is a common interview gotcha. The RAII fix is std::jthread (C++20), which auto-joins in its destructor.
The race condition
When two threads read-modify-write the same data without synchronization, the result is undefined and non-deterministic.
int counter = 0;
// two threads both doing: counter++; (read, add, write — NOT atomic)
// final value is unpredictable: increments get lostMutex + RAII locks
A mutex ensures only one thread enters a critical section at a time. Never lock/unlock by hand — use an RAII lock so it's released even on exceptions (RAII from Module 04, applied to locking).
#include <mutex>
std::mutex m;
int counter = 0;
void safe_increment() {
std::lock_guard<std::mutex> lock(m); // locks now, unlocks at scope exit
++counter; // protected critical section
} // lock released automatically herestd::lock_guard— simplest RAII lock: lock on construction, unlock on destruction.std::unique_lock— more flexible: can unlock/relock, and works with condition variables.
Deadlock
Two threads each hold a lock the other needs, and both wait forever.
// Thread A: lock(m1); lock(m2);
// Thread B: lock(m2); lock(m1); // opposite order — deadlock possible
// FIX 1: always acquire multiple locks in the SAME global order
// FIX 2: use std::scoped_lock to lock several at once, deadlock-free
std::scoped_lock lock(m1, m2); // C++17: locks both without deadlockstd::atomic
For simple shared values (counters, flags), an atomic gives lock-free thread-safe operations — no mutex needed.
#include <atomic>
std::atomic<int> counter{0};
++counter; // atomic: no race, no lock
std::atomic<bool> done{false}; // classic stop flag between threadsCondition variables — the producer-consumer pattern
A condition_variable lets a thread sleep until another signals that work is ready — the backbone of a work queue.
std::mutex m;
std::condition_variable cv;
std::queue<Task> q;
void producer(Task t) {
{ std::lock_guard<std::mutex> lk(m); q.push(t); }
cv.notify_one(); // wake a waiting consumer
}
void consumer() {
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, []{ return !q.empty(); }); // sleeps until predicate true; guards spurious wakeups
Task t = q.front(); q.pop();
}cv.wait(lk, predicate) re-checks the condition on wake, protecting against spurious wakeups. The bare cv.wait(lk) without a predicate is a bug waiting to happen. This exact detail shows up in concurrency interviews.
Exercise 10.1 — Thread-safe queue
- First, reproduce a race: two threads increment a plain
inta million times each; observe the total is wrong. Fix it two ways — with amutex, and withstd::atomic<int>. - Build a
ThreadSafeQueue<T>withpushand a blockingpopusingmutex+condition_variable(predicate form). - Run one producer thread and two consumer threads through it and confirm no items are lost or duplicated.
- Deliberately create a two-mutex deadlock, confirm it hangs, then fix it with
std::scoped_lock.
Takeaways
- Every
std::threadmust be joined or detached, or the program terminates. - Unsynchronized shared read-modify-write is a race condition — undefined and non-deterministic.
- Protect critical sections with a
mutexvia RAII locks (lock_guard/unique_lock), never manual lock/unlock. - Prevent deadlock with consistent lock ordering or
std::scoped_lock. atomicfor simple values;condition_variable(predicate form) for producer-consumer.
Loading starter code…
Checkpoint · Concurrency
6 questions · pass at 70%
cv.wait(lk, pred) rather than cv.wait(lk)?Finished Module 10?
Pass the quiz to complete it automatically.