The GIL, Honestly
One mutex around the interpreter lets only one thread run Python bytecode at a time — and yet x += 1 can still lose an update. Watch the race survive the GIL op by op, then pick threading, multiprocessing, or asyncio from a computed wall-clock, and see what 3.13 free-threading really changes.
Concept · Languages & Runtimes. The source ↗
A free, interactive, animated visual explainer of The GIL, Honestly — built to be understood, not skimmed.
Questions
- What does the Python GIL actually do?
- The Global Interpreter Lock is a single mutex the CPython interpreter holds while a thread runs bytecode, so only one thread executes Python bytecode at a time. The Python glossary defines it as "the mechanism used by the CPython interpreter to assure that only one thread executes Python bytecode at a time," which "simplifies the CPython implementation by making the object model (including critical built-in types such as dict) implicitly safe against concurrent access." Its main job is to protect internal state — most famously every object's reference count — from being corrupted by two threads at once.
- Does the GIL make my code thread-safe?
- No — and this is the trap. The GIL guarantees only that a single bytecode instruction runs atomically, not that a whole statement does. x += 1 compiles to four bytecodes (LOAD, load the constant, an add, and STORE), and the interpreter can switch threads between any two of them. Two threads incrementing the same counter can both read the old value, both add one, and both store the same result — one increment is silently lost, with the GIL fully on. You still need your own locks.
- When should I use threading vs multiprocessing vs asyncio in Python?
- Match the tool to the bottleneck. For I/O-bound work (network, disk, database calls) the GIL is released during the wait, so threading and asyncio both overlap the waiting — asyncio scales to far more concurrent tasks with less overhead, threading is simpler to retrofit. For CPU-bound work, threads cannot run Python in parallel, so you reach for multiprocessing, which the docs describe as "effectively side-stepping the Global Interpreter Lock by using subprocesses instead of threads" — at the cost of process-startup and inter-process-communication overhead.
- What is the switch interval and the convoy effect?
- sys.setswitchinterval controls "the ideal duration of the timeslices allocated to concurrently running Python threads" — 5 milliseconds by default. After that interval the running thread is asked to release the GIL so another can run; which thread actually gets it is the OS's decision, since "the interpreter doesn't have its own scheduler." The convoy effect is the honest wart: when a CPU-bound thread holds the GIL, an I/O thread that just became runnable can be repeatedly forced to wait out the full interval before it reacquires the lock, so mixing CPU-bound and I/O-bound threads can make the I/O work slower than running it alone.
- Does Python 3.13 remove the GIL?
- Not by default — it makes removing it possible. PEP 703 added a free-threaded build (a separate executable, usually python3.13t) where the GIL can be disabled so threads run Python in true parallel. The 3.13 docs are blunt that "the free-threaded mode is experimental and work is ongoing to improve it: expect some bugs and a substantial single-threaded performance hit," and C extensions must be rebuilt for it. The standard build keeps the GIL, so for now free-threading is opt-in and still maturing.