Mental model
The CPython Global Interpreter Lock (GIL) is a mutual exclusion lock preventing multiple OS threads from executing Python bytecode in parallel on multiple CPU cores.
Theory
For I/O-bound tasks (network requests, DB queries, file reads), threads yield the GIL during socket waits, making asyncio or threading highly efficient. For CPU-bound tasks (image processing, data transformation, matrix math), multiple Python threads contend for the GIL and run slower than single-threaded code. CPU workloads require multiprocessing or ProcessPoolExecutor to spawn separate Python interpreter instances on distinct CPU cores.
Alternatives and trade-offs
asyncio: Best for high-concurrency network I/O; low memory footprint (~kBs per coroutine).threading: Good for legacy blocking I/O calls; shares memory space but limited by GIL for CPU.multiprocessing: True multi-core CPU parallelism; high IPC serialization cost (pickle) and memory overhead per process.
Failure modes and misconceptions
- Threading for CPU math: Using Python
threading.Threadfor matrix calculation does not utilize multiple CPU cores due to GIL lock contention. - IPC Pickle Overhead: Passing huge objects across
ProcessPoolExecutortriggers slowpickleserialization.
Decision scenario
Combine asyncio for non-blocking HTTP network handling with ProcessPoolExecutor for offloading heavy image or data transformation tasks across CPU cores.
Learning outcomes
- Explain CPython GIL bytecode lock constraints across OS threads.
- Select between AsyncIO coroutines, threading, and multiprocessing.
- Optimize IPC data transfers for multi-process Python execution.
Trade-offs
Multiprocessing achieves true multi-core CPU parallelism, but incurs inter-process communication overhead and higher memory consumption per process worker.