A mutex answers “may I touch this?”. It cannot answer “is there anything to touch yet?”.
A consumer with an empty queue does not want the lock — it wants the queue to stop being empty. Spinning on it is the obvious approach and the wrong one:
while (queue.isEmpty()) { /* burn a core */ }That is 100% CPU to learn nothing.
The primitive
A condition variable lets a thread sleep until another thread says the state changed.
// Consumerlock();while (queue.isEmpty()) { condition.wait(); // releases the lock and sleeps, atomically}const job = queue.shift();unlock();
// Producerlock();queue.push(job);condition.notify(); // wake one waiterunlock();lock();
while (queue.isEmpty()) cond.wait();
job = queue.shift();
unlock();Consumer takes the lock and checks the queue. Empty.
while, never if
This is the rule people break, and it is worth understanding rather than memorising.
The general shape is: the condition variable is a hint that something may have changed. The predicate is the truth.
notify or notifyAll
notify() wakes one waiter. notifyAll() wakes all of them.
Use notify() when every waiter is waiting for the same thing and any one can
proceed — a pool of identical consumers.
Use notifyAll() when waiters are waiting for different predicates. With
one condition variable shared between “queue not empty” and “queue not full”,
notify() can wake a thread whose predicate is still false while the one that
could proceed stays asleep. That is a lost wakeup, and the program stops.
The safer default is notifyAll(); the faster one is a separate condition
variable per predicate, which is what a bounded queue actually does.
Prefer something higher level
Almost every use of a condition variable is one of these, and the library version is already correct:
- A bounded blocking queue — the producer–consumer case, which is most of them.
- A countdown latch — wait for N things to finish.
- A semaphore — wait for a permit.
- A future or promise — wait for one value.
- A channel — wait for a message.
Reach for a raw condition variable when your predicate is genuinely custom — “wait until the buffer has at least 4 KB and the connection is still open” — and there is no ready-made structure for it.