Code of the Day
AdvancedConcurrency & Performance

The GIL, threads, and processes

Why Python threads don't speed up CPU work — and what to use instead.

PythonAdvanced9 min read
Recommended first
By the end of this lesson you will be able to:
  • 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 , 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 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.

Finished reading? Mark it complete to track your progress.

On this page