Python/Python Internals/The Global Interpreter Lock (GIL)

The Global Interpreter Lock (GIL)

The GIL is a mutex in CPython that lets only one thread execute Python bytecode at a time — the single most misunderstood part of Python concurrency.

Advanced45mInterview Production

Learning objectives

  • Explain what the GIL protects and why CPython has it
  • Predict which workloads the GIL limits and which it doesn't
  • Choose between threading, multiprocessing and asyncio correctly
  • Describe how the GIL is released around I/O and in C extensions

Prerequisites

Threads vs processesReference countingCPython object model

Version notes

  • · Python 3.12 added per-interpreter GILs (PEP 684).
  • · Python 3.13 ships an experimental free-threaded build (PEP 703) that can disable the GIL — not yet the default.

Simple explanation

What it is. The Global Interpreter Lock is a single mutex the CPython interpreter holds while executing bytecode. Even on a 16-core machine, one CPython process runs Python bytecode on one core at a time.

Why it exists. CPython manages memory with reference counting. Incrementing/decrementing refcounts from multiple threads simultaneously would corrupt them without locking every object. One global lock is far simpler and faster for single-threaded code than fine-grained per-object locks.

The problem it solves. It makes CPython's memory management thread-safe and keeps single-threaded execution fast, at the cost of true multi-core parallelism for pure-Python CPU work.

How it works. A thread must hold the GIL to run bytecode. It releases the lock periodically (every few milliseconds) and — crucially — around blocking I/O and inside many C extensions (NumPy, database drivers). So threads overlap beautifully for I/O but not for CPU-bound Python loops.

Analogy: One microphone in a meeting room. Everyone can be in the room (threads), but only the person holding the mic can speak (run bytecode). When someone steps out to make a phone call (I/O), they hand the mic to someone else.

Common misconceptions

  • "Python can't do concurrency." — It can; the GIL limits parallel *CPU-bound bytecode*, not concurrency. I/O-bound threads and asyncio scale fine.
  • "Threads are useless in Python." — They're ideal for I/O-bound work, where the GIL is released during waits.
  • "Multiprocessing has a GIL problem too." — Each process has its own interpreter and its own GIL, so processes achieve real parallelism.

Common alternatives

  • multiprocessing / ProcessPoolExecutor for CPU-bound parallelism
  • asyncio for high-concurrency I/O without threads
  • C extensions / NumPy that release the GIL for heavy numeric work
  • The free-threaded (no-GIL) 3.13+ build for experimentation

Interactive visualization

Step 1 / 5
1from threading import Thread
2
3def burn():
4 total = 0
5 for _ in range(10_000_000):
6 total += 1 # pure-Python CPU work
7
8t1 = Thread(target=burn)
9t2 = Thread(target=burn)
10t1.start(); t2.start()

Threads (one GIL)

🎤Thread-1 (burn)running
Thread-2 (burn)waiting for GIL

Both threads start

Only the thread holding the GIL (🎤) runs bytecode. T1 acquires it; T2 must wait even though a core is free.

Code examples

IntermediateCPU-bound: threads don't help
python
import time
from concurrent.futures import ThreadPoolExecutor

def burn(n):
    x = 0
    for _ in range(n):
        x += 1
    return x

N = 20_000_000
start = time.perf_counter()
with ThreadPoolExecutor(max_workers=4) as ex:
    list(ex.map(burn, [N, N, N, N]))
print(f"threads: {time.perf_counter() - start:.2f}s")  # ~ same as serial
Expected output
threads: ~2.4s  (no speed-up: the GIL serialises the loops)

Note: Four CPU-bound threads take about as long as running them one after another.

Behind the scenes

  • The GIL is released around blocking syscalls (`time.sleep`, socket reads, file I/O) via `Py_BEGIN_ALLOW_THREADS`.
  • Since 3.2 the interpreter switches threads on a time interval (`sys.getswitchinterval()`, default 5ms) rather than counting bytecodes.
  • Reference counting is why the GIL exists: `Py_INCREF`/`Py_DECREF` are not atomic without it.
  • PEP 703 (free-threaded build) replaces refcount safety with per-object locking and biased reference counting.

Interview insights

Key definitions

  • · GIL: a mutex ensuring only one thread executes CPython bytecode at once, protecting reference counts.
What is the GIL and why does CPython have it?

Concise answer: A single interpreter-wide lock allowing one thread to run bytecode at a time. It exists to make reference-count-based memory management thread-safe and to keep single-threaded code fast.

Detailed answer: CPython reference-counts every object. Concurrent, unlocked refcount mutation would race and corrupt memory. Rather than lock every object, CPython uses one global lock. It's held while running bytecode and released around I/O and in cooperating C extensions. Consequence: threads give no speed-up for CPU-bound pure-Python work but scale well for I/O-bound work.

Common mistake: Claiming the GIL makes threads pointless — it doesn't for I/O.

Follow-ups:

  • · How would you parallelise a CPU-bound task?
  • · When is the GIL released?
  • · What changes in the free-threaded build?
You have a CPU-bound task that's slow. Threads didn't help. Why, and what do you do?

Concise answer: The GIL serialises CPU-bound bytecode, so threads can't run it in parallel. Use multiprocessing/ProcessPoolExecutor, or offload to a C/NumPy path that releases the GIL.

Detailed answer: Only one thread runs bytecode at a time, so four CPU-bound threads share one core's worth of Python execution. Switch to processes (each with its own GIL and memory), vectorise with NumPy (which releases the GIL for the heavy loop), or move the hot path into a C extension.

Common mistake: Adding more threads and expecting linear speed-up.

Follow-ups:

  • · What's the cost of multiprocessing? (IPC, pickling, memory)

Knowledge check

Multiple choiceQuestion 1 of 3

For a CPU-bound pure-Python workload, which gives real parallelism?

ScenarioQuestion 2 of 3

8 HTTP requests, each ~1s. Best tool?

Multiple choiceQuestion 3 of 3

Why does the GIL exist at all?

Score: 0/3