What does threading.Event give you over a plain boolean flag, and when is clear() unsafe?
answer
- A flag you can block on
- Level-triggered, not a pulse
- wait with a timeout beats sleeping
- Clearing it invites a missed signal
basics
~20 sthreading.Event is a flag other threads can block on: wait() sleeps until set() flips it, with no polling. It is level-triggered and wakes every waiter. clear() is unsafe for repeated handshakes, because a set/clear pair can be missed entirely by a thread that never got to run.
solid answer
~60 sA `threading.Event` is a boolean flag bundled with a condition, exposing `set()`, `clear()`, `is_set()` and `wait()`. The value over a bare `bool` is `wait()`: a thread blocks until the flag is set instead of spinning or sleeping in a poll loop, and `wait(timeout=...)` returns the flag's value — `True` if set, `False` on timeout — which makes it an *interruptible sleep*. That is why `while not stop.wait(1.0):` is the correct body for a periodic worker: shutdown takes microseconds rather than a full poll interval. An `Event` is level-triggered: once set, every current waiter is released and every later `wait()` returns immediately until someone calls `clear()`. That makes it ideal for one-shot signals — shutdown requested, configuration loaded, warm-up complete. It is the wrong tool for repeated round-trip signalling, because between `set()` and `clear()` a waiter that has not been scheduled yet sees nothing and misses the edge entirely. For repeated handoff use a `threading.Condition` with a predicate, or a thread-safe blocking channel. An `Event` also carries no payload and counts nothing.
code
python · 14 linesimport threading
stop = threading.Event()
def worker():
while not stop.wait(timeout=0.01):
pass
print("worker observed the stop signal")
t = threading.Thread(target=worker)
t.start()
stop.set()
t.join()
print("is_set:", stop.is_set())go deeper
Recall the four methods — set, clear, is_set, wait — and the one idiom that matters: while not stop.wait(interval): for a worker loop you can shut down promptly, instead of sleeping and checking a plain boolean.
Explain that the event is level-triggered, that wait returns the flag's value rather than raising on timeout, and that set wakes every waiter. Be able to describe the lost-signal window a set/clear pair opens.
Show how you wire shutdown through a service: one event shared by every worker, bounded waits everywhere, joins after set so cleanup completes. Say plainly when you would reach for a condition or a blocking channel instead.
Own the lifecycle contract across a fleet of workers — what "stopping" means, how in-flight work drains, what the deadline is before you stop waiting. Argue for one signalling convention rather than each component inventing its own.
### The object `threading.Event` holds one internal boolean and a condition variable that guards it. Four methods: * `set()` — flips the flag true and wakes **every** thread currently in `wait()`. * `clear()` — flips it back to false. Threads that call `wait()` afterwards block again. * `is_set()` — returns the flag without blocking. * `wait(timeout=None)` — returns immediately if the flag is already true; otherwise blocks until it is set or the timeout expires. The return value is the flag's value at that moment: `True` if the event is set, `False` if the wait timed out. ### Why not a plain bool A module-level `stopping = False` and a `time.sleep(1)` poll loop appears to work, and in a CPython build with the global interpreter lock the other thread will eventually observe the write. Three things are still wrong with it: * **Latency.** The worker notices shutdown on average half a poll interval late, and at worst a full one. With `stop.wait(1.0)` the same loop wakes immediately on `set()` and still ticks once per second otherwise, which is the pattern you want for a periodic task. * **The busy alternative is worse.** Poll without a sleep and you burn a core; poll with a long sleep and you are unresponsive. `wait()` removes the tradeoff by parking the thread in the OS until it is signalled. * **Portability of the assumption.** "The other thread will see the write" leans on CPython's interpreter lock. On the free-threaded build officially supported since Python 3.14 there is no interpreter-wide serialization, and coordination that relied on it is exactly what you should not be hand-rolling. `Event` is correct on every build. ### Level-triggered, and what follows from that An `Event` is a **latch**, not a pulse. After `set()`, the flag *stays* set: a thread that starts waiting a minute later returns instantly. This is precisely the property that makes it right for signals that are true once and stay true — "the service is shutting down", "the cache has been primed", "start-up finished, you may serve traffic". You cannot miss a signal you were late for, which is the single most common bug class in thread coordination. It is also why `clear()` is a trap. Consider two threads trying to use one event as a repeated handshake: the producer calls `set()` and then, shortly after, `clear()`. If the consumer was not scheduled in that window, it never observed the flag as true, and it now waits for a signal that has already come and gone. Nothing raises; the consumer hangs. There is no way to close that window with `Event` alone, because there is no atomic "wait, and consume the signal". The rule of thumb: use `clear()` only when a single thread owns the lifecycle and there are no waiters at the time — a re-arm before the next round, in a phase where everyone is known to be idle. For genuine repeated signalling use a `threading.Condition` with a predicate you check in a `while` loop, or a thread-safe blocking channel, both of which make the state — not the edge — the thing you wait on. ### What an Event cannot do * **Wake exactly one waiter.** `set()` releases all of them. If you need one-of-N handoff, that is a condition variable's `notify()` or a queue. * **Carry data.** The signal is one bit. Any payload must live somewhere else, guarded by its own lock, which brings back the visibility question that the event alone did not answer. * **Count.** Two `set()` calls in a row are indistinguishable from one. For counting, that is a semaphore. * **Rendezvous.** If N threads must all arrive before any proceeds, `threading.Barrier(parties)` is the primitive; each thread calls `wait()` and they are released together, with `threading.BrokenBarrierError` raised in every participant if one aborts or times out. ### The idiom worth memorising ```python stop = threading.Event() def periodic(): while not stop.wait(5.0): do_one_pass() cleanup() ``` One object gives you the interval, the shutdown signal and prompt cancellation. Pass the same event to every worker and a single `stop.set()` from the main thread brings them all down; join each thread afterwards so cleanup actually completes before the process exits.
- What does threading.Event.wait(timeout=0.5) return if the event is never set?`False` — the flag's value when the wait ends. It does not raise on timeout, so the return value is the only way to distinguish "signalled" from "gave up". A caller that ignores it and proceeds as though the event fired is the usual source of a shutdown that runs while the work is still in flight.
- How many waiting threads does threading.Event.set() release?All of them, and every subsequent `wait()` returns immediately until someone calls `clear()`. There is no way to signal exactly one waiter with an `Event`. When you need one-of-N handoff, use `threading.Condition.notify()` with a predicate, or hand the work item through a thread-safe blocking channel instead.
- How would you make a fixed group of threads all start a phase together?`threading.Barrier(parties)`: each thread calls `wait()` and none proceeds until the last one arrives, at which point all are released and the barrier resets for the next round. If a participant aborts or times out the barrier breaks and every participant sees `threading.BrokenBarrierError`, which is what stops the rest hanging forever.
An Event is a shop's OPEN sign, not a doorbell. Anyone who looks after it is switched on sees it, however late they arrive — but flip it on and straight back off and the person who blinked never knew.
saying these in an interview costs you the question
- Polls a boolean with time.sleep instead of Event.wait
- Uses set and clear as a repeated two-thread handshake
- Thinks Event.wait raises when the timeout expires
- Expects set to wake exactly one waiting thread
- Believes the event carries the data with the signal
- Assumes clear cancels a wait that already returned