How to start, join, and make daemon threads in Python
Starts one daemon and one non-daemon thread, joins the non-daemon thread, and shows how daemon threads exit when the main program ends.
Python code
30 linesimport threading
import time
import logging
logging.basicConfig(level=logging.INFO, format="%(threadName)s: %(message)s")
def worker(name, delay):
for i in range(3):
time.sleep(delay)
logging.info(f"{name} step {i}")
if __name__ == "__main__":
daemon_thread = threading.Thread(
target=worker,
args=("daemon", 0.2),
daemon=True,
name="DaemonThread"
)
main_thread = threading.Thread(
target=worker,
args=("non-daemon", 0.1),
daemon=False,
name="MainWorker"
)
daemon_thread.start()
main_thread.start()
main_thread.join()
logging.info("Main script finished, daemon may or may not complete")
Output
MainWorker: non-daemon step 0
DaemonThread: daemon step 0
MainWorker: non-daemon step 1
DaemonThread: daemon step 1
MainWorker: non-daemon step 2
DaemonThread: daemon step 2
Main script finished, daemon may or may not complete
How it works
Threads run concurrently, so the interleaving of log messages varies between runs. The join() call on main_thread makes the main program wait until that thread finishes before logging the final message. A daemon thread is killed as soon as the main program exits—here it finishes before join() returns because its delays are shorter, but if it were longer-running, it would be abruptly terminated. Non-daemon threads keep the program alive even after main() completes, which is why joining is needed for orderly shutdown. Logging with threadName makes each thread's output easy to distinguish.
Common mistakes
- Forgetting to call `join()`, so the program may exit before non-daemon threads finish.
- Assuming daemon threads complete—they are terminated when the main program ends.
- Starting a thread more than once, which raises `RuntimeError`.
Variations
- Use `threading.Thread(..., daemon=True)` in a `with` context via `concurrent.futures.ThreadPoolExecutor` for simpler lifecycle management.
- Pass `daemon=True/False` via a class that subclasses `threading.Thread` and overrides `run()`.
Real-world use cases
- Spawn background cleanup threads that must not block application shutdown.
- Offload periodic tasks (like heartbeat checks) to a daemon that dies with the main process.
- Wait for critical worker threads to finish before publishing results or closing resources.
Sponsored
More from Concurrency & performance
- Benchmark list.append vs deque.append in Python medium
- Build a Python Performance Profiler That Generates Readable Reports medium
- Graceful Shutdown Executor Context Manager in Python medium
- How to Build a Producer-Consumer Pattern with asyncio.Queue in Python medium
- How to Cancel an asyncio Task with Graceful Cleanup in Python medium
- How to Convert Data in Parallel with ThreadPoolExecutor in Python easy
Keep learning
Related tutorials and quizzes for this topic.