Signalling, rate limiting and rendezvous points are common needs that locks alone handle badly.
Beyond Lock, threading offers higher-level primitives:
- `Event`: a boolean flag.
event.set()wakes every thread blocked inevent.wait(timeout);clear()resets. Perfect for "stop now" and "ready" signals. A worker loop can checkwhile not stop.is_set():and sleep withstop.wait(interval), so it wakes immediately on shutdown instead of after a fullsleep. - `Semaphore(n)`: a counter allowing up to n holders. Use
with sem:to limit concurrent access to a resource, such as at most 5 simultaneous connections.BoundedSemaphoreraises if released more than acquired. - `Condition`: a lock plus wait/notify.
with cond: cond.wait_for(predicate)sleeps until another thread changes state and callscond.notify()/notify_all(). It's the building block of queues; preferqueue.Queueunless you need custom conditions. - `Barrier(n)`: n threads call
wait()and all are released together, for phased computations. - `RLock`: a lock the same thread may acquire repeatedly.
All waits take timeout=. Use timeouts in production code so a bug shows up as a timeout error rather than a silent hang. Never poll in a tight loop (while not flag: pass): it burns CPU and, with the GIL, slows the very thread you're waiting for.
Example
import threading, time
stop = threading.Event()
slots = threading.Semaphore(2)
active, peak, lock = 0, 0, threading.Lock()
def poller():
ticks = 0
while not stop.wait(0.05): # sleeps, but wakes at once on stop.set()
ticks += 1
print("poller stopped after", ticks, "ticks")
def use_resource():
global active, peak
with slots: # at most 2 threads inside
with lock: active += 1; peak = max(peak, active)
time.sleep(0.1)
with lock: active -= 1
p = threading.Thread(target=poller); p.start()
users = [threading.Thread(target=use_resource) for _ in range(6)]
for u in users: u.start()
for u in users: u.join()
stop.set(); p.join() # poller stopped after ~6 ticks
print("peak concurrent users:", peak) # peak concurrent users: 2Remember
- Event signals a flag to many waiting threads; wait(timeout) doubles as interruptible sleep
- Semaphore(n) caps concurrent access
- Condition and Barrier handle custom state changes and rendezvous
Watch out for
- Busy-waiting on a shared boolean instead of using Event.wait().