advanced

Multitasking & Multiprocessing in Python

7 min read

Overview

Multiprocessing enables true parallel execution by spawning separate Python processes—each with its own independent Python interpreter, memory space, and GIL instance. This allows Python applications to utilize 100% of all available CPU cores.

Learning Objectives

  • Differentiate between I/O concurrency (multithreading) and true CPU parallelism (multiprocessing).
  • Use ProcessPoolExecutor to compute heavy calculations across multiple CPU cores.
  • Safely pass data between processes using IPC queues and process pools.

Concept Explanation

In simple words:

  • Multithreading: Shares 1 memory address space under 1 GIL lock. Great for I/O waiting.
  • Multiprocessing: Spawns $N$ independent Python processes across $N$ CPU cores. Great for heavy math, data crunching, and image processing!
text
Main Process  --->  Fork/Spawn Child Process 1 (Core 1)
              --->  Fork/Spawn Child Process 2 (Core 2)
              --->  Fork/Spawn Child Process 3 (Core 3)

Code Examples

Example 1 — Parallel Computation with ProcessPoolExecutor

python
from concurrent.futures import ProcessPoolExecutor
import time

def compute_heavy_square(n):
    # CPU-bound calculation
    return sum(i * i for i in range(n))

if __name__ == '__main__':
    inputs = [10000000, 10000000, 10000000, 10000000]

    start_time = time.time()
    with ProcessPoolExecutor() as executor:
        results = list(executor.map(compute_heavy_square, inputs))

    elapsed = time.time() - start_time
    print(f"Parallel CPU computation finished in {elapsed:.2f} seconds!")

Example 2 — Comparing Concurrency vs Parallelism

| Feature | Multithreading (ThreadPool) | Multiprocessing (ProcessPool) | | :--- | :--- | :--- | | Best For | I/O-bound (Network, Web API, Disk) | CPU-bound (Data processing, Math) | | Memory | Shared memory space | Separate memory space per process | | GIL Bound? | Yes (GIL limits execution to 1 CPU core) | No (Bypasses GIL using separate processes) | | Overhead | Low memory overhead | Higher process startup overhead |

Visual Flow

The following execution flow represents parallel CPU multiprocessing:

⚡ Visual Execution Flow
Spawn Child ProcessesAssign Chunks to CPU Core 1, 2, 3, 4Execute 100% Parallel MathSerialize & Gather Results

Common Mistakes

  • Forgetting if __name__ == '__main__':: On Windows and macOS, multiprocessing requires entry point protection to prevent recursive process spawning loops.

Best Practices

  • Use ProcessPoolExecutor for CPU-intensive data transformations.
  • Limit worker process counts to match available CPU cores (os.cpu_count()).