std - condition_variable
WHY does this exist?
WHAT it is: a synchronization primitive (#include <condition_variable>) that works together with a std::mutex (via std::unique_lock) to let threads wait for a condition.
HOW the three pieces fit:
- A shared condition (e.g.
bool ready, a queue) protected by a mutex. - Waiter: locks mutex → calls
cv.wait(lock, pred)→ atomically unlocks + sleeps. - Notifier: locks mutex → changes condition → calls
cv.notify_one()/notify_all().

The core API (derived by reasoning, not memorized)
Why is the predicate while, not if? Two reasons baked into the standard:
- Spurious wakeups: the OS may wake a waiting thread even though nobody notified. The loop re-checks and goes back to sleep.
- Stolen wakeups: with
notify_all, several threads wake, but the first one to grab the lock may consume the only item. The loop makes the others re-check and sleep again.
Deriving the producer–consumer queue from scratch
We need: shared std::queue<int>, a std::mutex m, a std::condition_variable cv.
#include <condition_variable>
#include <mutex>
#include <queue>
#include <thread>
#include <iostream>
std::queue<int> q;
std::mutex m;
std::condition_variable cv;
bool done = false;
void producer() {
for (int i = 0; i < 5; ++i) {
{
std::lock_guard<std::mutex> lk(m); // lock to mutate shared state
q.push(i);
} // unlock BEFORE notify (small optimization, avoids waiter waking & blocking)
cv.notify_one(); // ring the doorbell
}
{
std::lock_guard<std::mutex> lk(m);
done = true;
}
cv.notify_all(); // wake everyone so they can exit
}
void consumer() {
while (true) {
std::unique_lock<std::mutex> lk(m); // unique_lock: wait can unlock/relock it
cv.wait(lk, []{ return !q.empty() || done; }); // sleep until pred true
while (!q.empty()) {
int v = q.front(); q.pop();
std::cout << "got " << v << "\n";
}
if (done && q.empty()) break; // nothing left and producer finished
// lk auto-unlocks at end of loop scope
}
}Second example: a one-shot "go" signal
Common mistakes (Steel-manned)
Flashcards
What header declares std::condition_variable?
<condition_variable>Which lock type must a thread pass to cv.wait and why?
std::unique_lock<std::mutex>, because wait must atomically unlock then re-lock the mutex (lock_guard can't).What does cv.wait(lock) do atomically?
Why must the wait condition be checked in a loop (or via the predicate overload)?
Difference between notify_one and notify_all?
notify_one wakes a single waiting thread; notify_all wakes all of them (they then contend for the lock).Why modify the shared state while holding the mutex before notifying?
Does a condition_variable remember notifications sent when no one is waiting?
Is it necessary to hold the lock while calling notify?
What's cv.wait(lk, pred) equivalent to?
while(!pred()) cv.wait(lk);Recall Feynman: explain to a 12-year-old
Imagine you're a worker waiting for pizza. Instead of standing at the oven checking every second (tiring = wasted energy), you go nap on the couch. There's a rule: before napping you tell the chef "wake me when it's ready" — and you let go of the kitchen so the chef can cook. When the pizza is done the chef shouts ("notify!") and you wake up, double-check the pizza is really there (maybe you just dreamed the shout — that's a spurious wakeup), and then eat. The couch nap = wait, the shout = notify, double-checking = the while loop. That way you use no energy while sleeping but never miss your pizza.
Connections
- std::mutex — the lock the condition_variable cooperates with.
- std::unique_lock — required by
wait; enables unlock/relock. - std::lock_guard — simpler RAII lock, used by the notifier side.
- Producer-Consumer Pattern — the canonical use case.
- std::future and std::promise — higher-level one-shot signaling.
- Spurious Wakeup — the reason for the predicate loop.
- std::atomic — alternative for lock-free flags (but no efficient blocking).
- Race Condition / Deadlock — the bugs proper use prevents.
Concept Map
Hinglish (regional understanding)
Intuition Hinglish mein samjho
Socho ek consumer thread ko queue se item chahiye, lekin queue khaali hai. Agar wo while(empty){} karke baar-baar check karta rahe, to CPU 100% jal jayega — ye busy-wait bekaar hai. std::condition_variable ka kaam yahi hai: thread ko so jaane do (0% CPU) jab tak condition true na ho. Jab producer item daal de, wo doorbell bajata hai yaani notify_one(), aur sleeping thread jaag jaata hai.
Important baat: wait() ko std::unique_lock chahiye, lock_guard nahi. Kyunki wait ko mutex unlock karke sona padta hai (taaki producer lock le sake aur data change kar sake), phir jagne par dobara lock karta hai. Aur hamesha predicate wali wait use karo: cv.wait(lk, []{ return !q.empty(); });. Ye andar se while loop hai — spurious wakeup (bina notify ke OS jaga de) aur stolen wakeup (doosra thread item le jaaye) dono se bachata hai.
Sabse badi galti: shared flag ko bina mutex ke change karna. Isse lost wakeup ho sakta hai — waiter ne check kiya flag false hai, abhi sleep karne hi waala tha, tab tak notifier flag set karke notify kar deta hai, par koi sun nahi raha — waiter hamesha ke liye so gaya. Fix: flag ko hamesha usi mutex ke andar change karo jo waiter use karta hai. Yaad rakho: condition_variable ki koi memory nahi hoti — agar notify tab bheja jab koi wait nahi kar raha, wo notify gum ho jaata hai. Isliye correctness shared flag se aati hai, notification se nahi.
Mantra: "Wait under lock, wake with notify, aur predicate ko hamesha loop mein check karo."