Multithreading and Multiprocessing in Python
Python multithreading example using threading module
Python is widely used for tasks like data processing, web development, automation, and machine learning. When working with performance-heavy tasks, developers often rely on multithreading and multiprocessing to boost efficiency. While both improve performance, they work differently and are suitable for different scenarios.
In this guide, we’ll explore multithreading and multiprocessing in Python with examples, use cases, and best practices.
Multithreading is a programming technique that allows multiple threads to run concurrently within a single process. Each thread shares the same memory space, making it lightweight but sometimes prone to issues like race conditions.
I/O-bound tasks (waiting for external resources).
Network operations (API requests, web scraping).
File read/write operations.
GUI applications.
import threading
import time
def print_numbers():
for i in range(5):
print(f"Number: {i}")
time.sleep(1)
def print_letters():
for letter in "ABCDE":
print(f"Letter: {letter}")
time.sleep(1)
# Creating threads
t1 = threading.Thread(target=print_numbers)
t2 = threading.Thread(target=print_letters)
# Starting threads
t1.start()
t2.start()
# Waiting for threads to finish
t1.join()
t2.join()
print("Multithreading complete!")
Multiprocessing allows multiple processes to run independently, each with its own memory space. It bypasses Python’s Global Interpreter Lock (GIL), making it ideal for CPU-bound tasks.
CPU-heavy operations (mathematical computations, data crunching).
Image processing.
Machine learning model training.
Parallel execution of independent tasks.
import multiprocessing
import time
def square_numbers(numbers):
for n in numbers:
print(f"{n} squared is {n*n}")
time.sleep(1)
if __name__ == "__main__":
numbers = [1, 2, 3, 4, 5]
p1 = multiprocessing.Process(target=square_numbers, args=(numbers,))
p1.start()
p1.join()
print("Multiprocessing complete!")
Feature | Multithreading | Multiprocessing |
---|---|---|
Memory Usage | Shared memory | Separate memory |
Best for | I/O-bound tasks | CPU-bound tasks |
Speed | Faster context switching | True parallelism |
Risk | Race conditions | Higher memory usage |
GIL (Global Interpreter Lock) | Affected by GIL | Not affected |
Use multithreading for I/O tasks like network calls or file handling.
Use multiprocessing for CPU-heavy tasks like computations.
Avoid unnecessary threads/processes — they add overhead.
Use Python’s concurrent.futures for simplified thread/process management.
Always use if __name__ == "__main__":
when working with multiprocessing (to prevent infinite process spawning).
Both multithreading and multiprocessing are powerful tools in Python. While multithreading improves performance in I/O-heavy applications, multiprocessing shines in CPU-intensive workloads. Mastering both will help you build efficient, scalable, and high-performing Python applications.