🧵 Python Multithreading – Create, Sync, and Communicate Threads
🧲 Introduction – Why Use Multithreading in Python?
Python is known for its simplicity and readability, but did you know it also supports multithreading?
Multithreading is a powerful way to:
- Run multiple tasks concurrently
- Optimize I/O-bound operations
- Build responsive applications (e.g., GUI, network services)
Even with Python’s Global Interpreter Lock (GIL), multithreading is ideal for I/O-bound tasks like file I/O, network calls, or database operations.
🎯 In this guide, you’ll learn:
- Thread lifecycle in Python
- Creating, starting, joining threads
- Main thread, daemon threads, thread priorities
- Thread pools for scalable parallelism
- Synchronization, deadlocks, and inter-thread communication
🔁 Thread Lifecycle in Python
A Python thread goes through several states:
State | Description |
---|---|
New | Thread is created but not started yet |
Runnable | Thread is ready to run (started) |
Running | Actively executing instructions |
Blocked | Waiting for I/O or a lock |
Terminated | Execution completed or stopped |
🧠 Python threads are managed by the OS scheduler; Python doesn’t expose all states explicitly.
🛠️ Create / Start / Join Threads
✅ Creating a Thread with threading.Thread
import threading
def task():
print("Thread is running")
t = threading.Thread(target=task)
t.start() # Starts the thread
t.join() # Waits for the thread to finish
💡 Use join()
to synchronize the main thread with the child thread.
✅ Thread with Arguments
def greet(name):
print(f"Hello {name}")
t = threading.Thread(target=greet, args=("Alice",))
t.start()
t.join()
✅ Using Subclassing
class MyThread(threading.Thread):
def run(self):
print("Thread via subclass")
t = MyThread()
t.start()
t.join()
🧵 Thread Pools with concurrent.futures
Managing many threads manually is hard. Thread pools offer a higher-level abstraction.
from concurrent.futures import ThreadPoolExecutor
def square(n):
return n * n
with ThreadPoolExecutor(max_workers=3) as executor:
results = executor.map(square, [1, 2, 3, 4])
print(list(results))
✅ Output:
[1, 4, 9, 16]
💡 Ideal for batch processing, parallel tasks, and web scraping.
🧭 Main Thread / Daemon / Priority
✅ Main Thread
import threading
print("Main thread:", threading.current_thread().name)
🧠 Python automatically starts a main thread when your program begins.
🔥 Daemon Threads
Daemon threads run in the background and automatically terminate when the main program exits.
def background_task():
while True:
print("Running in background")
t = threading.Thread(target=background_task, daemon=True)
t.start()
💡 Daemons are great for logging, heartbeat monitors, and cleanups.
⏫ Thread Priority
Python’s threading
module does not support priority control directly. Thread scheduling is left to the OS.
📌 If you need more control, consider multiprocessing or external libraries like psutil
.
🔐 Synchronization and Deadlocks
Without synchronization, race conditions can occur when multiple threads modify shared data.
✅ Using threading.Lock
lock = threading.Lock()
counter = 0
def safe_increment():
global counter
for _ in range(100000):
with lock:
counter += 1
⚠️ Deadlock Example
lock1 = threading.Lock()
lock2 = threading.Lock()
def thread1():
with lock1:
with lock2:
print("Thread1 acquired both locks")
def thread2():
with lock2:
with lock1:
print("Thread2 acquired both locks")
⚠️ These two threads could deadlock by waiting for each other’s lock.
💡 Avoid nested locks or use threading.RLock()
when needed.
📬 Inter-thread Communication
Python threads share memory. Common communication tools include:
✅ Shared Variables with Lock
queue = []
def producer():
with lock:
queue.append("data")
def consumer():
with lock:
if queue:
print(queue.pop(0))
✅ queue.Queue
for Thread-safe Messaging
import queue
q = queue.Queue()
def producer():
q.put("message")
def consumer():
msg = q.get()
print("Received:", msg)
threading.Thread(target=producer).start()
threading.Thread(target=consumer).start()
✅ queue.Queue
is thread-safe and handles blocking automatically.
📘 Best Practices for Python Multithreading
✅ Do This | ❌ Avoid This |
---|---|
Use threading.Lock() for shared data | Modifying shared variables without a lock |
Use ThreadPoolExecutor for parallel tasks | Creating hundreds of threads manually |
Use join() to wait for thread completion | Ignoring thread synchronization |
Use daemon=True only for background tasks | Relying on daemons for important work |
📌 Summary – Recap & Next Steps
Python multithreading lets you build concurrent, responsive, and efficient applications—especially for I/O-bound tasks.
🔍 Key Takeaways:
- ✅ Use
threading.Thread
orThreadPoolExecutor
for concurrent tasks - ✅ Understand the thread lifecycle: create → start → run → terminate
- ✅ Use locks and queues for synchronization and communication
- ✅ Daemon threads die with the main program; regular threads don’t
- ⚠️ Deadlocks can occur—always design thread-safe logic
⚙️ Real-World Relevance:
Used in web scrapers, network services, chat apps, logging systems, and asynchronous I/O operations.
❓ FAQ – Python Multithreading
❓ Can Python use multiple threads for CPU-bound tasks?
❌ No. Due to the GIL, use multiprocessing for CPU-bound operations.
❓ What is the difference between threading
and multiprocessing
?
threading
= shared memory, ideal for I/O-bound tasksmultiprocessing
= separate memory, ideal for CPU-bound tasks
❓ What is a daemon thread?
✅ A background thread that automatically terminates when the main program exits.
❓ Can threads share data?
✅ Yes. Threads share memory, but access must be synchronized.
❓ What is the safest way to communicate between threads?
✅ Use queue.Queue
– it’s thread-safe and handles locking internally.
Share Now :