What is a Condition?
A Condition is used when threads need to:
- wait until a condition becomes true (e.g., “buffer not empty”)
- notify others when the state changes
It combines:
- a lock
- a wait/notify mechanism
Producer-consumer with Condition
import threading
import time
items = []
cond = threading.Condition()
def producer():
for i in range(5):
time.sleep(0.2)
with cond:
items.append(i)
print("produced", i)
cond.notify() # wake one waiting consumer
def consumer():
for _ in range(5):
with cond:
while not items:
cond.wait() # release lock and wait
v = items.pop(0)
print("consumed", v)
p = threading.Thread(target=producer)
c = threading.Thread(target=consumer)
p.start(); c.start()
p.join(); c.join()Why the while loop?
Always re-check the condition after waking:
- spurious wakeups can happen
- another consumer might have consumed the item first
The problem a Condition solves
Suppose a consumer thread needs an item that is not there yet. It could check in a loop, again and again, wasting CPU. Or it could sleep for a while and risk reacting late. A Condition gives a better answer: the consumer sleeps until another thread tells it the state has changed, using no CPU while it waits.
How wait and notify work
A condition wraps a lock. Everything happens inside with cond:. Calling cond.wait() does three things atomically: it releases the lock, puts the thread to sleep, and re-acquires the lock when the thread is woken. Releasing the lock is essential, because otherwise the producer could never get in to add an item. cond.notify() wakes one waiting thread and cond.notify_all() wakes them all. The woken thread continues only after the notifier leaves its with block and frees the lock.
Tracing the example
The consumer enters the block and finds items empty, so it waits. Every 0.2 seconds the producer takes the lock, appends an item, prints, and calls notify(). The consumer wakes, pops the item, and leaves the block. The print("consumed", v) sits outside the lock on purpose, so other threads are not held up while printing.
Waiting on a predicate
Because the wait-in-a-loop pattern is so common, Condition has a shortcut, wait_for, which takes a function and keeps waiting until it returns true:
import threading
import time
ready = False
cond = threading.Condition()
def waiter() -> None:
with cond:
cond.wait_for(lambda: ready) # sleeps until ready is True
print("go!")
def setter() -> None:
global ready
time.sleep(0.3)
with cond:
ready = True
cond.notify_all()
t1 = threading.Thread(target=waiter)
t2 = threading.Thread(target=setter)
t1.start(); t2.start()
t1.join(); t2.join()wait_for checks the predicate first, so it also works if ready was already true.
Common mistakes
- Using
ifinstead ofwhile. After waking, the state may already have been taken by another consumer, so re-check it in a loop or usewait_for. - Calling
waitornotifywithout holding the lock. Python raises aRuntimeError. - Changing the state without notifying. The waiter sleeps forever.
- Notifying before the state changes. Change the data first, then notify.
When to use it
Use a Condition when threads must wait for a state to become true, such as a buffer having room or a flag being set. If you only need a simple producer-consumer hand-off, queue.Queue does all of this for you and is usually the better choice.
🧪 Try It Yourself
Exercise 1 – Condition notify and wait
Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.
Press Run to see what your code prints.Exercise 2 – notify_all
Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.
Press Run to see what your code prints.Exercise 3 – Predicate Wait
Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.
Press Run to see what your code prints.