Async programming
- Python supports both
concurrencyandparallelism - https://docs.python.org/3/library/concurrency.html
GIL (Global Interpreter Lock)
- It's a mechanism used in
CPython(the Python interpreter) to ensure that only one thread can be executed at a time -
While the interpreter is being "used" by a thread, it gets locked so that no other thread can use it at the same time
-
With that, the GIL
prevents race conditionsandprevent memory corruptionwhen multiple threads access Python objects simultaneously. It alsosimplifies memory managementandsimplifies garbage collection. -
CPU-bounds tasks: cannot be run in parallel, because the GIL allows only one thread to execute Python code at a time.
-
I/O-bound tasks: can be run in parallel, because these tasks go to a separate "queue" while waiting for the response, therefore releasing the lock
-
The
multiprocessingmodule bypasses the GIL limitations by using separate processes, each with its own interpreter and memory space.
Modules
threading (python 1.5+ 1999)
- Uses
threading.Thread - Limited by the GIL: not good for CPU-bound tasks, but fine for I/O-bound tasks
-
Lightweight compared to processes
-
On the OS, it starts only one process with N threads
multiprocessing (python 2.6+ 2008)
- Uses
multiprocessing.Process - Spawns separate Python processes, each with its own GIL
- Therefore, each process can run its own thread: Offers
true parallelismacross CPU cores -
The drawback is the overhead: it has its own interpreter and its own memory space
-
On the OS, it starts multiple processes with one or more threads
concurrent.futures (python 3.2+ 2011)
- High-level API over
threadingandmultiprocessing ThreadPoolExecutor— pool of threads, good for I/O-bound tasks (threading)ProcessPoolExecutor— pool of processes, good for CPU-bound tasks (multiprocessing)- Preferred over raw
threading/multiprocessingin most real code — simpler interface, manages the pool lifecycle
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
# I/O-bound
with ThreadPoolExecutor(max_workers=10) as executor:
futures = [executor.submit(fetch, url) for url in urls]
results = [f.result() for f in futures]
# CPU-bound
with ProcessPoolExecutor() as executor:
results = list(executor.map(cpu_heavy_fn, items))
asyncio (python 3.4+ 2014)
- Uses
async-awaitsyntax - Introduces
Coroutines, which are lightweight "threads" managed by the python runtime (similar to java virtual threads) - It's the preferred way to implement I/O-bound parallelism
- The
threadingmodule is now mostly used for compatibility with libraries that are not async-aware. Also theconcurrent.futures.ThreadPoolExecutoris mostly "deprecated" in favor of asyncio
GIL removal (Python 3.13+)
- Python 3.13 introduced an experimental free-threaded mode (PEP 703) that can be enabled at build time
- Removes the GIL, allowing true multi-threaded parallelism for CPU-bound tasks without
multiprocessing - Still experimental — some C extensions and libraries are not yet compatible
- Run with
python3.13t(thetbuild) or compile CPython with--disable-gil