Skillversitas

The Global Interpreter Lock

The GIL (Global Interpreter Lock) is a mutex inside CPython that allows only one thread to execute Python bytecode at a time. It exists because CPython's reference counting - the mechanism that frees objects - is not thread-safe, and a GIL-free implementation would require fine-grained locking everywhere, which would slow down single-threaded code. The practical consequence: threads in CPython do not speed up CPU-bound Python code.

What It Means in Practice

import time
from concurrent.futures import ThreadPoolExecutor

def cpu_bound(n):
    total = 0
    for i in range(n):
        total += i * i
    return total

N = 2_000_000

t0 = time.perf_counter()
cpu_bound(N); cpu_bound(N)
serial = time.perf_counter() - t0

t0 = time.perf_counter()
with ThreadPoolExecutor(max_workers=4) as pool:
    list(pool.map(cpu_bound, [N, N]))
parallel = time.perf_counter() - t0

print(f"serial {serial:.2f}s, 4 threads {parallel:.2f}s")
# 4 threads: roughly the same as serial - the GIL serializes the loops

For I/O-bound work the GIL is irrelevant: while a thread waits on a socket or file, it releases the GIL, and another thread can run. That is why thread pools are still the right tool for HTTP and database fan-out.

When Threads Do Help

  • Any work that waits: requests.get, cursor.execute, open() reads.
  • Code that runs in C and releases the GIL - NumPy array math, image libraries, hashing - where parallel threads genuinely run in parallel.
  • Long-running C extension calls that explicitly release the lock.

Ways Around the GIL

  • Processes: each process has its own interpreter and its own GIL (covered in the multiprocessing chapters).
  • C extensions / libraries: NumPy, Pandas hot paths, and most scientific C code release the GIL during heavy computation.
  • Free-threaded CPython (3.13+, experimental): a no-GIL build (PEP 703) that uses per-object locking; enabled with a special build flag, still maturing as of 3.13/3.14.
  • Other implementations: Jython and PyPy (with its experimental GIL support) behave differently, and GraalVM Python has no GIL.

Summary

The GIL serializes Python bytecode across threads, so CPU-bound Python code does not scale with threads - use processes or GIL-releasing C code for that. I/O-bound code is unaffected, which is why threading remains a first-class option for most real-world workloads.