Multithreading and Multiprocessing in Python

In Python, multithreading and multiprocessing are techniques used to perform concurrent execution of tasks, but they have different purposes, advantages, and limitations.

Multithreading

  • Concurrency within a single process: Multithreading is a way to achieve concurrency within a single process. It allows multiple threads (lightweight sub-processes) to run within the same memory space and share data.
  • Global Interpreter Lock (GIL): Python has a Global Interpreter Lock (GIL) that restricts multiple native threads from executing Python bytecode simultaneously. Python threads may not fully utilize multiple CPU cores in CPU-bound tasks.
  • Suitable for I/O-bound tasks: Multithreading is well-suited for I/O-bound tasks, such as network communication or file I/O, where threads can wait for external resources.
  • Shared memory: Threads share memory space and can easily exchange data, but this also requires careful synchronization to avoid data races and conflicts.

Multi-Threading Example

Here is an example of using multithreading in Python

import threading

def print_numbers():
    for i in range(1, 6):
        print(f"Number {i}")

def print_letters():
    for letter in 'CODE':
        print(f"Letter {letter}")

# Create two threads
thread1 = threading.Thread(target=print_numbers)
thread2 = threading.Thread(target=print_letters)

# Start the threads
thread1.start()
thread2.start()

# Wait for both threads to finish
thread1.join()
thread2.join()

Multiprocessing

  • Parallelism across processes: Multiprocessing allows you to achieve true parallelism by creating multiple separate processes, each with its own Python interpreter and memory space. This enables better utilization of multiple CPU cores.
  • No GIL limitation: Multiprocessing does not suffer from the Global Interpreter Lock (GIL) limitation, making it suitable for CPU-bound tasks that can take full advantage of multiple cores.
  • Multiprocessing module: Python provides the multiprocessing module to create and manage multiple processes, allowing you to execute functions or classes in parallel processes.

Multiprocessing Example

Here is an example of using multiprocessing in Python

import multiprocessing

def square(number):
    result = number * number
    print(f"The square of {number} is {result}")

if __name__ == "__main__":
    numbers = [1, 2, 3, 4, 5]
    
    # Create a multiprocessing Pool to parallelize the square function
    with multiprocessing.Pool() as pool:
        pool.map(square, numbers)

In summary, use multithreading when dealing with I/O-bound tasks that involve waiting for external resources. Use multiprocessing for CPU-bound tasks that can benefit from true parallelism and take advantage of multiple CPU cores. Be aware of the GIL in multithreading, which can limit its performance in certain scenarios.