Python Multithreading and Multiprocessing
Python provides two main modules for handling concurrent execution: threading and multiprocessing. Understanding the difference between them is crucial for writing efficient concurrent programs, especially due to Python's Global Interpreter Lock (GIL).
The Global Interpreter Lock (GIL)
The GIL is a mutex that allows only one thread to hold the control of the Python interpreter. This means that even in a multi-threaded architecture with more than one CPU core, only one thread can execute Python bytecode at a time.
- I/O-bound tasks: The GIL is released during I/O operations, so multithreading works well here.
- CPU-bound tasks: The GIL becomes a bottleneck, so multiprocessing is preferred.
Multithreading
The threading module is used for running multiple threads (tasks, function calls) at the same time. It is best suited for I/O-bound tasks (e.g., network operations, file I/O).
Example
import threading
import time
def print_numbers():
for i in range(5):
time.sleep(1)
print(f"Number: {i}")
def print_letters():
for letter in 'abcde':
time.sleep(1)
print(f"Letter: {letter}")
t1 = threading.Thread(target=print_numbers)
t2 = threading.Thread(target=print_letters)
t1.start()
t2.start()
t1.join()
t2.join()
print("Done!")
Multiprocessing
The multiprocessing module allows you to create processes that run independently. Each process has its own Python interpreter and memory space, bypassing the GIL. This is ideal for CPU-bound tasks (e.g., heavy computations).
Example
import multiprocessing
import time
def square_numbers():
for i in range(5):
time.sleep(1)
print(f"Square: {i * i}")
if __name__ == "__main__":
p1 = multiprocessing.Process(target=square_numbers)
p1.start()
p1.join()
print("Done!")