Python: the GIL and asyncio
- Last updated
- Prerequisites:
- Python threading and async basics
- Understanding of OS threads and event loops
- python
- gil
- asyncio
- concurrency
- cpython
- threading
Read at your depth
The practical view
The GIL (Global Interpreter Lock) is a mutex in CPython that allows only one thread to execute Python bytecode at a time. That is why threading in CPython does not speed up CPU-bound work: threads interleave but never run in parallel on multiple cores. For I/O-bound work (network, disk), threads still win because the GIL is released during blocking calls. asyncio is single-threaded cooperative concurrency: async def functions, await points, and an event loop (asyncio.run(...)) that switches between coroutines when they await I/O. Use asyncio for high-concurrency I/O; use multiprocessing (separate interpreters, no GIL sharing) for CPU-bound parallelism.
Legacy vs modern
Thread pool for CPU work vs asyncio for I/O work
Threads cannot parallelize Python bytecode under the GIL; asyncio scales concurrent I/O on a single thread without OS-thread overhead.
from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor(8) as pool:
results = list(pool.map(cpu_heavy, data))async def main():
async with aiohttp.ClientSession() as session:
results = await asyncio.gather(*(fetch(session, u) for u in urls))Interview gotchas
Context
The interviewer wants the mechanism, not the slogan — reference counting, bytecode serialization, and the switch interval are the key details.
The mechanical answer
CPython serializes bytecode execution with the GIL: at any moment one thread owns the interpreter (acquired for ~5ms of bytecode or until a blocking call). CPU-bound Python therefore interleaves rather than parallelizes. The GIL protects the interpreter's invariants: object reference counts (a racing Py_DECREF could free an object still in use), the cyclic GC's state, and global interpreter structures. It is released around blocking C-level operations (socket reads, file I/O), which is why I/O-bound threading still scales. PEP 703 free-threaded builds remove the GIL by making refcounting per-object (immortal objects) and adding fine-grained locks — at a performance cost for single-threaded workloads.
Trap
The naive answer is 'the GIL prevents race conditions in user code' — it only protects interpreter internals, not your code (your data races still exist). Another trap: 'threads are useless in Python' — they are correct and valuable for I/O-bound concurrency; the GIL is released during blocking calls. Mentioning the switch interval and Py_BEGIN_ALLOW_THREADS demonstrates you know how the lock is actually released and reacquired.
Context
The interviewer wants the architectural difference — cooperative single-threaded scheduling versus preemptive multi-threading — and the practical decision rules.
The mechanical answer
asyncio runs one thread with an event loop: coroutines suspend at await points, and the loop drives I/O through the OS selector (epoll/kqueue). Handling 10,000 connections costs 10,000 coroutines (small stack frames) plus open sockets — no OS threads. Threads give preemptive concurrency (no await discipline needed) but cost ~1MB stack + kernel resource per thread, and under the GIL still serialize bytecode. Choose asyncio when concurrency is dominated by I/O and your code can be structured cooperatively; choose threads when mixing blocking libraries (requests, psycopg2 sync) or CPU-bound C extensions without refactoring to async; choose processes for CPU parallelism. The hybrid (asyncio.to_thread) mixes both.
Trap
The trap answer is 'asyncio is faster than threads'. It reduces overhead and scales I/O concurrency, but a single blocking call inside a coroutine stalls the whole loop — preemptive threads at least keep other threads running. Another trap: 'asyncio uses threads under the hood' — the core loop is single-threaded; only integrations (to_thread, executor defaults) use threads. Candidates who name the selector, await discipline, and the blocking-call-stalls-loop failure mode demonstrate production asyncio experience.