multithreading · advanced
Concurrency: condition_variable — Wait Loop Pattern, Spurious Wakeup, notify_one vs all
condition_variable: thread sleeps until signaled. Requires unique_lock<mutex>. cv.wait(lk, pred): atomically releases mutex and sleeps; on wakeup reacquires mutex and rechecks pred. Equivalent to while(!pred) cv.wait(lk). Predicate handles: 1) spurious wakeups (POSIX allows wake without notify); 2) lost notifications (pred already true → returns immediately). Notifier: set state under mutex, then notify_one/all (preferred outside mutex to avoid re-sleep). notify_one: wake one waiter; notify_all: broadcast (shutdown, config). wait_for(lk, dur, pred): returns bool; true=pred satisfied, false=timeout. Use steady_clock. 3 deadly bugs: raw wait (no pred → spurious wakeup), unprotected shared flag (data race), notify inside mutex (legal but causes waiter contention). C++20 std::semaphore is simpler for counting/signaling without predicate complexity.
cv.wait(lk, pred): atomically release mutex and sleep; on wakeup: reacquire mutex + check pred; equivalent to while(!pred) cv.wait(lk); handles spurious wakeups. Set shared state under mutex BEFORE notify. notify_one/notify_all. Always use predicate form.
The code
// Condition variable — thread waits for a condition, woken by another thread
mutex mtx;condition_variable cv;bool ready = false;
// CORRECT: always use cv.wait with a predicate (while-loop equivalent)void waiter() { unique_lock<mutex> lk(mtx); cv.wait(lk, [] { return ready; }); // spurious-wakeup safe // ready is guaranteed true here, mutex re-acquired}
// WRONG: raw cv.wait() without predicate — spurious wakeup bugvoid waiter_buggy() { unique_lock<mutex> lk(mtx); cv.wait(lk); // may return even if !ready (spurious wakeup!) // ready might be false — SILENT BUG}
void notifier() { { lock_guard<mutex> lk(mtx); ready = true; } // set state UNDER MUTEX cv.notify_one(); // or notify_all()}
// cv.wait(lk, pred) is EXACTLY equivalent to:// while (!pred()) cv.wait(lk);
// wait_for — timeout (avoid starvation):auto status = cv.wait_for(lk, 100ms, [] { return ready; });if (status == cv_status::timeout) { /* handle timeout */}
// wait_until — absolute time:cv.wait_until(lk, system_clock::now() + 1s, [] { return ready; });
// notify_one: wake ONE waiting thread// notify_all: wake ALL waiting threads (use when any could proceed)
// DEADLOCK patterns:// 1. notify BEFORE wait: notifier sets ready before waiter calls wait → ok ONLY with predicate// 2. forgot to hold mutex when calling notify: data race on ready// 3. no spurious-wakeup protection (raw wait): incorrect state after wakeupWhat this lesson walks through
- 01condition_variable — wake thread when condition becomes true
- 02Spurious wakeup — the silent bug in raw cv.wait()
- 03The lost notification — when notifier runs before waiter
- 04notify_one vs notify_all — which to use
- 05wait_for and wait_until — timeouts
- 06Common cv pitfalls — the 3 deadly bugs
condition_variable allows a thread to sleep until another thread signals it. It requires a mutex (unique_lock). The canonical pattern: waiter acquires mutex, calls cv.wait(lk, predicate) — this atomically releases the mutex and sleeps; when woken, reacquires mutex and rechecks predicate. The notifier sets shared state under the mutex, then calls notify_one(). Without the predicate, spurious wakeups cause silent bugs.
See it animated — step by step, at your own pace
Unlock the full interactive walkthrough of Concurrency: condition_variable — Wait Loop Pattern, Spurious Wakeup, notify_one vs all and 100+ animated C++ interview lessons.