5.2.25 · D1C++ Programming

Foundations — std - condition_variable

1,700 words8 min readBack to topic

Before you can read the parent note, you need to see what a thread is, what "shared memory" looks like, why two threads touching the same box at once is dangerous, and what a lock physically does. This page builds every one of those pictures from nothing.


1. A thread — a worker with its own hands

Picture a kitchen. One cook = one thread. Two cooks = two threads working in parallel. They share the same kitchen (the same memory), but each has their own hands and their own place in their own recipe.

Figure — std - condition_variable

Why the topic needs it: a condition_variable only makes sense when one worker must wait for another worker. No second thread, no waiting, no doorbell.


2. Shared memory — the one box both workers reach into

Look at figure s01 again: the red box in the middle is one variable both cooks can touch. When cook A writes and cook B reads, they are talking through that box — that is the only way threads communicate.

Why the topic needs it: the "condition" a thread waits for is always a fact about shared state ("is the queue non-empty?"). Without a shared box, there is nothing to wait for.


3. The race condition — why sharing is dangerous

Say the shared box holds the number 5, and both cooks run "read it, add 1, write it back". You'd expect 7. But watch the timeline:

Figure — std - condition_variable

Both read 5 before either writes, both compute 6, both write 6. One +1 vanished. The bug appears only sometimes, depending on who was faster — that unpredictability is the "race".

Why the topic needs it: the whole reason wait and notify are entangled with a mutex is to prevent races on the shared condition. You cannot understand the API until you feel this danger.


4. The mutex — a single key for the box

Picture a bathroom with one key on a hook. You take the key, go in, lock the door. Anyone else who wants in stands outside until you come out and hang the key back.

Figure — std - condition_variable

Because only the key-holder can enter, only one thread modifies the box at a time — the vanished-+1 race from figure s02 becomes impossible.

Why the topic needs it: cv.wait takes a locked mutex, works with it, and hands it back. The mutex is half of every condition-variable pattern.


5. RAII locks — lock_guard and unique_lock

Holding the key by hand is error-prone: if you forget to hang it back (an early return, an exception), everyone else waits forever — a Deadlock. C++ fixes this with RAII: a small object grabs the key when it is created and hangs it back automatically when it goes out of scope (the closing }).

Why the topic needs it: the parent note uses lock_guard for the producer (lock, push, auto-unlock) and unique_lock for the consumer (because it calls wait). Knowing the difference is knowing why those two choices differ.


6. #include, ::, and <> — reading the notation itself

Three pieces of pure C++ syntax appear on every line of the parent note. Let's earn each symbol.

Why the topic needs it: every symbol on the parent page — std::condition_variable, std::unique_lock<std::mutex>, std::queue<int> — is built from exactly these three notations. Now none of them is mysterious.


7. The predicate P() — a yes/no question about the box

The []{ ... } is C++'s way of writing a throwaway function inline. Read []{ return ready; } as "the question: is ready true right now?"

Why the topic needs it: the predicate is the "some fact becomes true" from the core idea. It is the bridge between "a thread is waiting" and "for what."


8. Spurious wakeup — why we loop, not if

Because a wakeup might be fake, the thread must re-check the predicate every time it wakes — which is exactly why the equivalence above is a while loop, not a single if. Wake, ask "is it really true?", and if not, go back to sleep.

Why the topic needs it: this single fact is the reason cv.wait(lk, pred) exists at all instead of a plain "sleep until poked."


Prerequisite map

thread

shared state

race condition

mutex

lock_guard and unique_lock

predicate P

spurious wakeup

condition_variable

Read top to bottom: threads make sharing possible, sharing creates races, the mutex tames races, RAII locks wield the mutex safely, the predicate says what to wait for, spurious wakeups force the loop — and all of it converges on std::condition_variable.


Equipment checklist

A thread is
one line of execution — a single worker following one recipe step by step.
Shared state is
a variable that more than one thread can read or write; the only way threads talk.
A race condition is
when the final result depends on unpredictable timing of overlapping accesses.
A mutex is
a lock with one key; only its holder may touch the shared state, forcing one-at-a-time access.
std::lock_guard vs std::unique_lock
both auto-unlock at scope end, but only unique_lock can unlock and re-lock mid-scope — which cv.wait requires.
std:: and :: mean
std is the standard-library namespace (drawer); :: means "reach into that drawer for this name."
<int> in std::queue<int> means
fill in the blank — a queue that holds int values.
A predicate is
a function returning true/false, here written as a lambda []{ return ...; }, stating the condition to wait for.
A spurious wakeup is
the OS waking a sleeper with no notification, which is why we re-check the predicate in a loop.
cv.wait(lock, P) is equivalent to
while(!P()) cv.wait(lock); — keep sleeping while the answer is "no."