The GIL, threads, and processes
Why Python threads don't speed up CPU work — and what to use instead.
- Explain what the GIL is and what it prevents
- Choose threads for I/O-bound work and processes for CPU-bound work
- Reach for the right tool from the concurrent.futures toolbox
Python's concurrency story surprises newcomers: adding threads often makes CPU-heavy code no faster. The reason is the GIL, and understanding it tells you exactly which tool to reach for. This is the concurrency fundamentals lesson made specific to CPython.
What the GIL is
The Global Interpreter Lock is a lock inside CPython that allows only one thread to execute Python bytecode at a time. Even on an 8-core machine, your Python threads take turns running Python code — they don't run in parallel.
So threads can't speed up CPU-bound work (number crunching): they just interleave on one core. What the GIL doesn't block is waiting — when a thread is blocked on I/O (network, disk), it releases the GIL so another can run.
Threads vs processes
This gives a clean rule:
- I/O-bound work (web requests, file/database access) → threads. They spend most of their time waiting, and the GIL is released during that wait, so threads overlap effectively.
- CPU-bound work (parsing, math, image processing) → processes. Each process has its own interpreter and its own GIL, so they genuinely run in parallel across cores — at the cost of heavier startup and no shared memory.
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
# many downloads (I/O-bound): threads overlap the waiting
with ThreadPoolExecutor() as pool:
results = list(pool.map(fetch, urls))
# heavy computation (CPU-bound): processes use multiple cores
with ProcessPoolExecutor() as pool:
results = list(pool.map(crunch, datasets))concurrent.futures gives both behind one interface — switching is a one-line
change once you've diagnosed which kind of work you have.
First answer one question: is this code waiting, or computing? "Waiting" wants threads (or async, next); "computing" wants processes. Diagnosing that — the measure-first habit — matters more than any concurrency trick.
Where to go next
For I/O-bound work at high scale, there's an even lighter-weight model than threads. Next: asyncio.