How Python Free-Threaded Builds Remove the GIL

Free-threaded builds in modern Python, introduced experimentally in Python 3.13 via PEP 703, remove the Global Interpreter Lock (GIL) to achieve true multi-core CPU parallelism for multithreaded code. This article explains the technical mechanics behind this architectural shift, detailing how CPython replaces the blanket protection of the GIL with biased reference counting, immortal objects, thread-safe memory management, and fine-grained locking.

The Problem the GIL Solved

CPython traditionally used the GIL to protect its internal state and memory management systems. Because Python relies on reference counting to track object lifecycles, every time a thread accesses or releases an object, its reference counter changes. Without the GIL, multiple threads modifying these counters simultaneously would cause race conditions, resulting in memory leaks or premature memory deallocation (use-after-free errors). The GIL prevented this by enforcing that only one thread could execute Python bytecode at a time.

Biased Reference Counting

The primary challenge in removing the GIL was maintaining reference count accuracy without adding severe performance overhead from atomic operations. Free-threaded Python solves this using biased reference counting:

Immortal Objects

CPython creates many objects that exist for the entire duration of a program's execution, such as None, True, False, small integers, and core runtime types. In standard builds, these objects still undergo continuous reference count updates.

Free-threaded builds rely heavily on immortal objects (introduced in PEP 683). These objects have a dedicated bit pattern in their reference counter marking them as permanent. The runtime simply skips reference count increments and decrements for immortal objects, completely eliminating concurrent memory access conflicts for the interpreter's most frequently shared values.

Thread-Safe Memory Allocation via mimalloc

The standard Python memory allocator (pymalloc) was designed under the assumption that the GIL guarantees single-threaded access. Free-threaded builds replace this architecture with mimalloc, a high-performance, thread-safe memory allocator originally developed by Microsoft.

mimalloc provides thread-local allocation pools and free lists. This design allows threads to allocate and free memory blocks concurrently without contending for a single global allocator lock, dramatically reducing thread contention during object creation.

Fine-Grained Locking and Critical Sections

Without the universal lock of the GIL, shared data structures like dictionaries, lists, and user-defined objects need protection from concurrent mutations. Free-threaded Python replaces the single global lock with critical sections and fine-grained mutexes:

Thread-Safe Garbage Collection

CPython's cyclical garbage collector (GC) must periodically scan objects to identify and clean up cyclic references that reference counting alone cannot resolve. In free-threaded builds, the GC cannot safely inspect objects while threads are modifying them in the background. To handle this, the runtime coordinates brief stop-the-world phases—pausing worker threads momentarily to safely traverse memory graphs and isolate reference cycles without data corruption.