sleep() and wait() both move a thread into the Waiting state, but they mean very different things. sleep() is a timed pause that keeps any locks the thread holds. wait() is for coordination: it releases the lock and waits until another thread signals. Mixing them up is a common source of stalls in concurrent code.
In Python, these are time.sleep() and threading.Condition.wait().
sleep() keeps the lock, waits for a timer, then goes back to the ready queue on its own.
wait() releases the lock and waits until another thread calls notify() / notify_all() (or an optional timeout expires).
sleep(): "wake me after N seconds"#
A sleeping thread simply gives up the CPU for a fixed time.
import time
time.sleep(5) # don't need the CPU for 5 seconds
- The thread moves to Waiting (a timed wait).
- It does not release any locks it holds.
- When the timer expires, the OS moves it back to Ready. Nobody has to wake it.
wait(): "I can't continue until someone tells me"#
A waiting thread hands back the lock and sleeps until another thread signals that the condition may have changed.
import threading
cond = threading.Condition()
data_ready = False
def consumer():
with cond:
while not data_ready:
# Releases cond's lock and sleeps; re-acquires it before returning
cond.wait()
# data_ready is True and this thread holds the lock again
print("got data")
def producer():
global data_ready
with cond:
data_ready = True
cond.notify_all() # wake the waiters
t = threading.Thread(target=consumer)
t.start()
producer()
t.join()
- The thread moves to Waiting.
- It releases the condition's lock, so other threads can enter and change the condition.
- It stays waiting until another thread calls
notify()ornotify_all()on the same condition (or until the timeout, if you callwait(timeout)). - You must hold the lock to call
wait(), or Python raisesRuntimeError. - Always check the condition in a
whileloop (or usecond.wait_for(predicate)): threads can wake spuriously, or another thread may have consumed the condition first.
Why the lock difference matters#
Suppose Thread 1 holds lock A and then pauses. What happens to Thread 2, which also needs A, depends on how Thread 1 pauses:
Thread 1 calls sleep(5) |
Thread 1 calls wait() |
|
|---|---|---|
| Before pausing | Holds lock A | Holds lock A |
| Lock A while paused | Still held | Released |
| Thread 2 needs A | Blocked for 5 s | Proceeds now |
Calling sleep() while holding a lock is usually a bug: everyone else waits for no reason. wait() exists precisely so the thread that will satisfy the condition can get in.
yield(): a hint, not a wait#
Some runtimes offer a "yield" call that offers the CPU to other threads without waiting for anything. In Python, time.sleep(0) plays this role (on Unix, os.sched_yield() is the direct system call).
import time
time.sleep(0) # willing to give up the CPU, but not required to
The thread stays Ready (runnable) and only hints that others may go first. The scheduler is free to ignore it. You'll rarely need it.
Comparison#
The three calls differ in state, locks and who wakes the thread.
sleep() |
wait() |
yield | |
|---|---|---|---|
| State | Waiting (timed) | Waiting | Ready |
| Releases lock? | No | Yes | N/A |
| Wakes when | Timer expires | notify()/notify_all() or timeout |
Scheduler decides |
| Purpose | Timed pause | Coordinating threads | Scheduler hint |
| Typical use | Polling delays, simple throttling | Producer–consumer, condition waiting | Almost never |
Key takeaways#
sleep()pauses for a fixed time and keeps every lock it holds.wait()releases the lock, waits for a signal, and re-acquires the lock before returning.- Always call
wait()while holding the condition's lock, in awhileloop that re-checks the condition. - Sleeping while holding a lock is almost always a bug.
- Yielding is only a hint; the thread stays runnable.