Condition Variables
A condition variable lets a thread sleep until another thread announces that some state has changed. It is the right tool when "proceed" depends on a predicate, not on a fixed number of permits: the consumer waits until the queue is non-empty, the worker waits until its job is marked done. In Python it is the threading.Condition class, which always wraps a lock.
wait and notify
wait(timeout=None)- releases the underlying lock and blocks until notified (or the timeout expires), then re-acquires the lock before returning.notify(n=1)- wakes up tonwaiting threads.notify_all()- wakes every waiting thread.
The critical discipline: wait() must be called while holding the condition's lock, and the predicate must be re-checked in a loop after waking, because spurious wakeups and competing consumers both exist.
Producer-Consumer
import threading
import time
buf = []
cv = threading.Condition()
def producer():
for i in range(5):
with cv:
buf.append(i)
cv.notify()
time.sleep(0.1)
def consumer():
for _ in range(5):
with cv:
while not buf: # loop: re-check the predicate
cv.wait(timeout=5)
item = buf.pop(0)
print("consumed", item)
pt = threading.Thread(target=producer)
ct = threading.Thread(target=consumer)
pt.start(); ct.start()
pt.join(); ct.join()
Signaling Completion
Condition variables also implement the "wait for all workers" pattern without join(): each worker, when it finishes, increments a counter under the lock and notifies; the coordinator waits until the counter reaches the expected total:
done = 0
cv = threading.Condition()
def worker():
global done
do_work()
with cv:
done += 1
cv.notify_all()
with cv:
while done < 4:
cv.wait(timeout=30)
Summary
Use Condition when a thread should block until a state predicate becomes true. Always wait in a while not predicate loop while holding the condition's lock, notify after mutating the state, and prefer notify() when only one waiter can act on the change.