2 min read

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!")

programming/python/python