The GIL is a mutex that serializes bytecode execution in CPython to protect interpreter state
The GIL is a single mutex held by the thread that is currently executing Python bytecode. Only one thread runs Python code at a time, even on a multi-core machine. It exists because CPython's memory management is reference counting, and reference count updates are not atomic. Without a global lock, two threads could decrement the same object's refcount simultaneously and either free memory twice or leak it, and other shared interpreter structures such as the object allocator, interned strings, and the small-int cache would race. Making every refcount atomic would be possible but historically imposed a large single-threaded slowdown, so CPython chose a coarse lock that is cheap when uncontended. The GIL is not a language feature: it is a CPython implementation detail, and Jython and IronPython never had one.
The GIL is released periodically so other threads can run, and it is released around blocking IO and some C extension calls.
It protects interpreter-level state, not your application state. You still need locks for your own shared mutable data.
Workloads that release the GIL in C (numpy, hashlib, zlib, psycopg, file IO) can run in parallel across threads.
Trade-off: the GIL makes single-threaded CPython fast and C extensions simple, at the cost of CPU parallelism for pure Python threads.
Common mistake: claiming Python is single-threaded. It is not; multiple threads exist but only one executes bytecode at a time.
Common mistake: thinking the GIL makes your code thread-safe. It does not; individual bytecode operations are atomic, but sequences of them are not.
Version note: PEP 703 proposes an optional no-GIL build, and free-threaded builds became experimentally available in 3.13. Do not assume this in production yet; verify the build and library support.
0-2 years experience
2-5 years experience
5-8 years experience
8+ years experience