Multitasking & Multiprocessing in Python
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
ProcessPoolExecutorto 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!
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
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:
Common Mistakes
- Forgetting
if __name__ == '__main__':: On Windows and macOS, multiprocessing requires entry point protection to prevent recursive process spawning loops.
Best Practices
- Use
ProcessPoolExecutorfor CPU-intensive data transformations. - Limit worker process counts to match available CPU cores (
os.cpu_count()).