CodeNebula
Python⚡Concurrencyintermediate

The Global Interpreter Lock (GIL)

Python · Concurrency · Run with python3

Explanation

The GIL is a mutex in CPython that allows only one thread to execute Python bytecode at a time, even on multi-core CPUs. It exists because CPython's memory management (reference counting) is not thread-safe. The GIL makes single-threaded programs faster (no locking overhead) and C extension writing simpler. The consequence: CPU-bound multi-threaded Python programs don't scale across cores. I/O-bound workloads are largely unaffected — the GIL is released while waiting for I/O. Python 3.13 introduced an optional free-threaded build (`python3.13t`) that removes the GIL.

Real-World Scenario

Two threads computing prime numbers simultaneously on a 16-core machine run at the same total speed as one thread — the GIL serializes them. The same two threads making HTTP requests run twice as fast — each thread releases the GIL while waiting for the network, so they truly overlap. ```python import threading import time def cpu_bound(n: int) -> int: """Count-down loop — pure Python, GIL-bound.""" while n > 0: n -= 1 return n N = 10_000_000 # Single-threaded baseline start = time.perf_counter() cpu_bound(N) cpu_bound(N) single = time.perf_counter() - start # Multi-threaded — GIL prevents true parallelism t1 = threading.Thread(target=cpu_bound, args=(N,)) t2 = threading.Thread(target=cpu_bound, args=(N,)) start = time.perf_counter() t1.start(); t2.start() t1.join(); t2.join() multi = time.perf_counter() - start print(f"Single: {single:.2f}s") print(f"Multi: {multi:.2f}s (should be ~same or slower due to GIL)") ```

Quick Snippet

snippet.py
import threading
import time

def cpu_bound(n: int) -> int:
    """Count-down loop — pure Python, GIL-bound."""
    while n > 0:
        n -= 1
    return n

N = 10_000_000

# Single-threaded baseline
start = time.perf_counter()
cpu_bound(N)
cpu_bound(N)
single = time.perf_counter() - start

# Multi-threaded — GIL prevents true parallelism
t1 = threading.Thread(target=cpu_bound, args=(N,))
t2 = threading.Thread(target=cpu_bound, args=(N,))
start = time.perf_counter()
t1.start(); t2.start()
t1.join(); t2.join()
multi = time.perf_counter() - start

print(f"Single: {single:.2f}s")
print(f"Multi:  {multi:.2f}s  (should be ~same or slower due to GIL)")

Full Example

gil_demo.py
# Compile: python3 gil_demo.py
# Output:
# Single and multi-threaded times printed (multi ~= single due to GIL)

import threading
import time

def cpu_bound(n: int) -> int:
    while n > 0:
        n -= 1
    return n

N = 2_000_000

start = time.perf_counter()
cpu_bound(N)
cpu_bound(N)
single = time.perf_counter() - start

t1 = threading.Thread(target=cpu_bound, args=(N,))
t2 = threading.Thread(target=cpu_bound, args=(N,))
start = time.perf_counter()
t1.start(); t2.start()
t1.join(); t2.join()
multi = time.perf_counter() - start

print(f"Single-threaded: {single:.3f}s")
print(f"Multi-threaded:  {multi:.3f}s")
print(f"Ratio: {multi/single:.2f}x (expect ~1.0 due to GIL)")
print("For CPU-bound work, use multiprocessing instead.")
Run command
python3 gil_demo.py