How Python 3.13 Disables the GIL at Compile Time

Python 3.13 introduces an experimental free-threaded build mode based on PEP 703, allowing the CPython interpreter to execute multi-threaded Python code without the bottleneck of the Global Interpreter Lock (GIL). Disabling the lock requires building CPython from source using a dedicated configuration flag that replaces the standard locking architecture with atomic operations, biased reference counting, and thread-safe memory management. This article breaks down the compile-time build configuration, preprocessor macros, and internal CPython architectural shifts that remove the GIL.

The --disable-gil Configuration Flag

To compile CPython 3.13 without the GIL, the source must be built using the --disable-gil configuration flag during the configure phase:

./configure --disable-gil
make
make test
sudo make install

When this flag is passed to the build script, it triggers changes in the C compiler toolchain, fundamentally altering how the interpreter handles concurrency and memory management.

The Py_GIL_DISABLED Preprocessor Macro

The --disable-gil option sets the C preprocessor macro Py_GIL_DISABLED to 1 across all compiled translation units. In standard builds, CPython relies on PyThreadState and internal macros (such as Py_BEGIN_ALLOW_THREADS and Py_END_ALLOW_THREADS) to release and acquire the GIL mutex around blocking system calls or long-running computations.

Under Py_GIL_DISABLED:

Thread-Safe Memory and Biased Reference Counting

Because standard non-atomic reference counting (Py_INCREF and Py_DECREF) causes race conditions when multiple threads execute simultaneously without the GIL, compiling with Py_GIL_DISABLED activates alternative reference-counting mechanisms:

  1. Biased Reference Counting (BRC): Objects are marked with the thread ID that created them. The owning thread increments and decrements reference counts using non-atomic operations, while non-owning threads record increments and decrements via thread-local message queues or atomic instructions, minimizing performance degradation from atomic contention.
  2. Immortal Objects: Foundational objects (like None, True, False, small integers, and built-in types) have their reference counts fixed with special bit flags so that modifications to their counts are bypassed entirely.
  3. Mimalloc Integration: Memory allocation is swapped to a thread-safe allocator based on Microsoft's mimalloc. This provides isolated thread-local memory pools, preventing threads from contending for global allocation locks when creating or destroying objects.

Distinct ABI Designation

Because the internal object layout and reference counting changes break standard C API compatibility, the build system differentiates the compiled output using a distinct Application Binary Interface (ABI) tag.

A free-threaded build appends a t suffix to binary names and wheel tags (e.g., python3.13t and cp313-cp313t). This ABI distinction guarantees that binary C extensions compiled for standard GIL-enabled Python cannot be accidentally loaded into a free-threaded interpreter, preventing undefined behavior and memory corruption.

Runtime Fallback Support

Although the lock is dismantled at compile time, the free-threaded binary retains an optional, coarse-grained lock mechanism that can be dynamically toggled at runtime. Users running a --disable-gil build can pass the environment variable PYTHON_GIL=1 or the command-line flag -X gil=1 to reactivate a global lock if they encounter compatibility problems with legacy C extensions. By default, however, the compiled binary executes entirely lock-free.