When releasing threads blocked on a condition variable, when is waking a single waiter correct and when must you wake all of them? Describe what goes wrong in each direction.
answer
- signal one only if: same predicate, interchangeable, one-for-one
- two predicates on one condition -> wrong waiter eats the wake-up
- state flag / shutdown / batch of K -> broadcast
- broadcast = correct but O(N) wakeups, thundering herd
- structural fix: one condition per predicate
basics
~20 sWaking one is safe only when every waiter on that condition waits for the same predicate and one unit of state satisfies exactly one waiter. Otherwise wake all: the single wake-up can reach a thread whose predicate is false, which consumes it and leaves the right thread asleep. Waking all is always correct but costs a thundering herd.
solid answer
~60 sWaking a **single** waiter is correct only when three conditions hold together: all waiters on that condition test the **same predicate**, they are **interchangeable** (any one can consume the state change), and the change enables **exactly one** of them. Break any of those and you must broadcast: - **Different predicates on one condition** (a bounded buffer using one condition for "not full" and "not empty"): the wake-up can land on a producer, which re-checks, finds its predicate false, and sleeps again — consuming the only notification while the consumer that should have run stays asleep. Symptom: an intermittent hang that disappears when you switch to broadcast. - **One change enables many waiters** (a state flag flips, a barrier trips, capacity jumps by K, shutdown begins): one wake-up releases one thread while the rest wait for something that already happened. Broadcast is always *correct* but not free: N threads wake, contend for one mutex, most re-test a false predicate and sleep again — a thundering herd, O(N) context switches and cache traffic for one useful unit of work. The structural fix that gets both is **one condition variable per predicate**, which lets you signal narrowly and correctly.
code
text · 9 lines// BROKEN: one condition serves both predicates
put: while size==C: cond.wait(m); push; cond.signal()
take: while size==0: cond.wait(m); pop; cond.signal()
-> a signal meant for a consumer can wake a producer, be re-checked,
found false, and consumed. Right thread never wakes.
// CORRECT: separate conditions, narrow signals
put: while size==C: notFull.wait(m); push; notEmpty.signal()
take: while size==0: notEmpty.wait(m); pop; notFull.signal()go deeper
Say waking all is always safe and waking one is only safe when all waiters want the same thing and only one can proceed; give shutdown as an obvious broadcast case.
State the three-part rule, show the bounded-buffer bug where a signal reaches a producer instead of a consumer, and give the fix of one condition variable per predicate.
Quantify the thundering herd (O(N) context switches and mutex contention per event), note that broadcast is a symptom-level fix, and mention fairness is not guaranteed by a single wake-up.
Discuss the design spectrum from one shared condition to one condition per predicate to one per waiter or key, chosen by waiter count, event rate, and tail-latency requirements, and prefer encapsulated structures over hand-rolled signalling at each call site.
## The decision rule Wake **one** only if all three hold: 1. **Single predicate** — every thread waiting on this condition variable is waiting for the same logical thing. 2. **Interchangeable waiters** — it does not matter which one proceeds. 3. **One-for-one enabling** — the state change satisfies exactly one waiter (one item pushed ⇒ one consumer can run). Otherwise **wake all**. When in doubt, wake all: over-waking costs performance, under-waking costs correctness. ## Failure mode A: the wrong waiter consumes the notification The classic case is a bounded buffer with a single condition variable: ``` // BROKEN: one condition, two predicates put(x): lock(m); while size == C: cond.wait(m) push(x); cond.signal(); unlock(m) take(): lock(m); while size == 0: cond.wait(m) pop(); cond.signal(); unlock(m) ``` With a full buffer and several blocked producers, a consumer takes an item and signals — and the wake-up may go to *another producer* if producers and consumers share the wait set. That producer re-checks, sees the buffer still effectively full or races and loses, and goes back to sleep. The notification is spent; a producer that could have proceeded never learns. Enough of these and the system deadlocks with work available. This is not a bug in the waiting loop — the loop behaves correctly by re-checking and sleeping. The bug is that a single wake-up was aimed at a wait set containing threads with different predicates. Two fixes: - **Broadcast** — correct, and the reason "changing signal to notifyAll made the hang go away" is such a common war story. But it is treating the symptom. - **Two condition variables, one per predicate** (`notFull`, `notEmpty`) — now a signal is precisely targeted, and a single wake-up is both correct and cheap. This is the structurally right answer. ## Failure mode B: one change enables many waiters Even with a single predicate, one wake-up is wrong if the change satisfies more than one waiter: - A latch or gate opens: everybody waiting should proceed, so broadcast. - Shutdown begins: every waiter must observe termination, so broadcast. - A resource pool's capacity is raised by K, or K items are added in a batch: either signal K times or broadcast. - A barrier's generation advances: all parties must be released. A useful test: "after this state change, how many waiters could make progress?" If the answer can exceed one, one wake-up is insufficient. ## The cost of broadcasting Broadcast makes all N waiters runnable. They then serialize on the single mutex — one succeeds, the rest re-test a false predicate and go back to sleep. Costs: - **O(N) context switches** per useful state change. - **Cache-line ping-pong** on the mutex and the guarded state across cores. - **Latency spike** for the one thread that can actually proceed, because it competes with N−1 threads doing pointless work. - **Worse under scale**: with a producer signalling frequently and hundreds of waiters, broadcast can dominate the workload — the thundering-herd problem. Some implementations mitigate with "wait morphing" — moving broadcast waiters directly to the mutex's queue rather than making them all runnable at once — which reduces but does not eliminate the cost. ## Fairness and starvation A single wake-up says nothing about *which* waiter is chosen. Implementations vary from FIFO to arbitrary, and barging threads that never waited can win the mutex anyway, so under signal-and-continue semantics a specific waiter can in principle be passed over repeatedly. If your design needs a fairness guarantee — bounded waiting for every thread — you must build it explicitly (ticket ordering, or a per-thread condition/handoff), not assume it from `signal`. For most workloads this is a non-issue, but it matters for latency SLOs at the tail. ## Advanced: one condition variable per waiter When waiters are *not* interchangeable — each waits for a specific resource, a specific key, or a specific turn — the general solution is to give each waiter (or each key) its own condition and wake exactly the right one. This is how fair handoff and specific-resource-ready notifications are built. It costs a condition object per waiter/key and more bookkeeping, and pays for itself when N is large and wake-ups are frequent. ## What to say in an interview State the decision rule, name both failure directions (missed wake-up leading to a hang; thundering herd leading to wasted CPU), and then give the structural answer: **one condition variable per predicate** is what lets you signal one waiter safely. Add that when in doubt, broadcast, because a performance problem is diagnosable and a hang may not be.
- A hang disappeared after changing signal to broadcast. What was probably wrong, and is the change a real fix?Almost certainly one condition variable served two different predicates, so a single wake-up was reaching a thread whose predicate was false; it re-checked, slept again, and consumed the only notification. Broadcast masks that by waking everyone, so the right thread eventually runs. The real fix is one condition variable per predicate, which restores correct, cheap, targeted signalling instead of paying an O(N) wake-up on every state change.
- When is waking a single waiter both correct and clearly preferable?When every waiter on that condition tests the same predicate, any of them can consume the change, and the change enables exactly one — a single item pushed into a non-empty-condition queue, or one permit returned to a pool. Then a single wake-up does exactly one useful thing and avoids waking N−1 threads that would only re-check and sleep, which matters a great deal when waiter counts are high and events are frequent.
- Does a single wake-up guarantee that the longest-waiting thread is chosen?No. Implementations may choose FIFO or arbitrarily, and under signal-and-continue semantics a thread that never waited can acquire the mutex first anyway. If bounded waiting or fairness is a requirement, it must be built explicitly — for example with ticket ordering or a dedicated condition per waiter — rather than assumed from the primitive.
Calling one name from a mixed waiting room where some people are waiting for the dentist and some for the optician: if you call without knowing who is who, you may summon someone who cannot be served, and they sit back down while the person you meant keeps waiting. Two separate queues fix it better than shouting at everyone.
saying these in an interview costs you the question
- Claiming waking one waiter is always safe as long as waiters re-check in a loop — the loop cannot help a thread that was never woken.
- Sharing one condition variable between a not-full and a not-empty predicate.
- Treating broadcast as free rather than an O(N) wake-up with mutex contention.
- Signalling once after adding a batch of K items or raising capacity by K.
- Assuming a single wake-up picks the longest-waiting thread or provides fairness.