The Global Interpreter Lock (GIL)
The Global Interpreter Lock (GIL)
Section titled “The Global Interpreter Lock (GIL)”What it is
Section titled “What it is”The GIL is a mutex in the reference implementation of Python (CPython) that allows only one thread to execute Python bytecode at a time, even on a multi-core machine. It exists because CPython’s memory management (reference counting) isn’t thread-safe by default — the GIL is the simplest way to make it safe without a slower, more complex fine-grained locking scheme.
Before this feature
Section titled “Before this feature”There was no “before” — the GIL has existed since CPython’s early multi-threading support, as the tradeoff that made threading in CPython possible at all without a full rewrite of the memory model. The relevant comparison isn’t “before/after” but “what does and doesn’t get around it.”
After this feature
Section titled “After this feature”import threadingimport time
def cpu_bound_work(): total = 0 for i in range(50_000_000): total += i return total
start = time.time()t1 = threading.Thread(target=cpu_bound_work)t2 = threading.Thread(target=cpu_bound_work)t1.start(); t2.start()t1.join(); t2.join()print(f"Threads: {time.time() - start:.2f}s") # roughly the SAME as running both sequentially
start = time.time()cpu_bound_work()cpu_bound_work()print(f"Sequential: {time.time() - start:.2f}s") # not much slower than the threaded version above# Multiprocessing sidesteps the GIL entirely — separate processes, separate GILsimport multiprocessingimport time
def cpu_bound_work(): total = 0 for i in range(50_000_000): total += i return total
if __name__ == "__main__": start = time.time() p1 = multiprocessing.Process(target=cpu_bound_work) p2 = multiprocessing.Process(target=cpu_bound_work) p1.start(); p2.start() p1.join(); p2.join() print(f"Multiprocessing: {time.time() - start:.2f}s") # ~half the time — true parallelismWhy this is better
Section titled “Why this is better”There’s no upside being “sold” here — the GIL is a constraint, and the practical takeaway is knowing when it does and doesn’t matter:
- I/O-bound work is unaffected: while a thread waits on a network call, file read, or
time.sleep(), it releases the GIL, sothreadinggenuinely helps with I/O-bound concurrency (web requests, file downloads). - CPU-bound work doesn’t parallelize across threads: pure-Python number crunching in multiple threads runs no faster than one thread, because only one can hold the GIL at a time.
multiprocessingsidesteps it: separate processes each get their own interpreter and GIL, so CPU-bound work genuinely runs in parallel — at the cost of higher memory use and slower inter-process communication than sharing memory in threads.
Key notes / edge cases
Section titled “Key notes / edge cases”- Many C-extension libraries (NumPy, for instance) release the GIL during their heavy computation, so
threadingcan speed up workloads that spend most of their time inside such a library, even though they’re “CPU-bound” in a loose sense. - A PEP 703 effort (free-threaded / “no-GIL” CPython) is an active, ongoing project as of recent Python versions — check the exact interpreter/version in use before assuming the GIL is unconditionally present.
- The GIL is a CPython implementation detail, not part of the Python language — other implementations (Jython, IronPython) don’t have it, though CPython is what “Python” means in the vast majority of real-world deployments.
Quick practice
Section titled “Quick practice”-
Does adding more threads speed up a pure-Python CPU-bound loop?
Answer
No — the GIL means only one thread executes Python bytecode at a time, so CPU-bound threads don't run in parallel. -
Why does
threadingstill help for I/O-bound tasks despite the GIL?Answer
A thread releases the GIL while it's blocked waiting on I/O (network, disk, sleep), letting other threads run during that wait — the GIL only blocks concurrent *Python bytecode execution*, not waiting. -
What’s the standard-library way to get true CPU parallelism in Python despite the GIL?
Answer
`multiprocessing` — separate OS processes, each with its own interpreter and GIL, genuinely run on separate cores.