multithreading
thread management
programming
concurrent computing
threads termination

How to exit all running threads?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Stopping all threads safely is not about killing them from the outside. In most applications, the correct approach is cooperative shutdown: signal every worker that the program is stopping, let each thread finish its current work, clean up resources, and then join the threads before the process exits.

Cooperative Cancellation with a Shared Signal

In Python, the most practical shutdown signal is threading.Event. Each worker checks the event periodically and exits its loop when the event is set.

python
1import threading
2import time
3
4stop_event = threading.Event()
5
6
7def worker(name: str) -> None:
8    while not stop_event.is_set():
9        print(f"{name} is working")
10        time.sleep(0.5)
11    print(f"{name} is shutting down cleanly")
12
13
14threads = [threading.Thread(target=worker, args=(f"worker-{i}",)) for i in range(3)]
15
16for thread in threads:
17    thread.start()
18
19time.sleep(2)
20stop_event.set()
21
22for thread in threads:
23    thread.join()
24
25print("All threads exited")

This pattern scales well because the shutdown logic is explicit. Every thread knows when it should stop and has a chance to release files, sockets, locks, or database connections before returning.

Why Forcing Thread Termination Is a Bad Idea

Many developers look for a global "stop all threads now" API. Most mainstream runtimes either do not provide one or strongly discourage it. Abrupt thread termination can leave shared state half-updated, locks permanently held, or buffered writes unfinished.

In Python specifically, there is no safe built-in mechanism for forcibly terminating arbitrary threads. That is by design. Threads share memory, so hard cancellation would easily corrupt program state.

Because of that, thread shutdown should be part of the thread design from the beginning. Long-running loops, polling workers, and consumer threads should all have an exit condition.

Handling Blocking Work

The usual complication is a thread blocked on I/O, queue.get(), or a long wait. Those threads still need to wake up often enough to notice the shutdown signal.

A queue consumer can use a timeout and recheck the event:

python
1import queue
2import threading
3
4stop_event = threading.Event()
5tasks = queue.Queue()
6
7
8def consumer() -> None:
9    while not stop_event.is_set():
10        try:
11            item = tasks.get(timeout=0.5)
12        except queue.Empty:
13            continue
14
15        try:
16            print(f"processing {item}")
17        finally:
18            tasks.task_done()
19
20
21thread = threading.Thread(target=consumer)
22thread.start()
23
24tasks.put("job-1")
25tasks.put("job-2")
26
27tasks.join()
28stop_event.set()
29thread.join()

Timeout-based loops are simple and predictable. Another common pattern is pushing a sentinel value such as None into the queue so the worker knows there is no more work.

Daemon Threads Versus Graceful Shutdown

Daemon threads are terminated automatically when the main program exits. That can be tempting, but it is rarely the right answer for important work. A daemon thread may be cut off while writing a file or sending a network request.

Use daemon threads only for truly disposable background activity. For anything that owns resources or must complete a transaction, prefer normal threads plus an explicit shutdown path.

Designing for Application Shutdown

A clean shutdown sequence usually looks like this:

  1. Stop accepting new work.
  2. Signal existing workers to exit.
  3. Unblock any workers waiting on queues or I/O.
  4. Join every thread.
  5. Exit the process only after cleanup is complete.

That structure also makes signal handling easier. For example, a KeyboardInterrupt handler can set the event and then join worker threads before the program terminates.

Common Pitfalls

A common mistake is setting a Boolean flag without any synchronization discipline and assuming every thread will see it immediately. In Python, threading.Event communicates intent more clearly and provides the right coordination primitive.

Another problem is forgetting about blocked threads. A stop flag alone does nothing if a worker is waiting forever on a queue or socket read. Add timeouts or another way to wake the worker.

The last mistake is skipping join(). If the main thread exits too early, the process may end before workers finish cleanup, especially if those workers were marked as daemons.

Summary

  • Safe thread shutdown is cooperative, not forced.
  • 'threading.Event is a simple and reliable stop signal in Python.'
  • Blocking operations need timeouts, sentinels, or another wake-up mechanism.
  • Daemon threads trade correctness for convenience and should be used sparingly.
  • Signal, unblock, join, and then exit.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.