DEV Community

zakaria chatouane
zakaria chatouane

Posted on

Python 3.13 No-GIL: What You Need to Know

Introduction

The Global Interpreter Lock (GIL) is a core component of CPython that has been part of the interpreter since the 90s. Essentially, it’s a mutex that ensures only one thread executes Python bytecode at a time, protecting internal data structures—especially the reference counting used by the garbage collector—from race conditions. read more about GIL.

Excitingly, Python 3.13 is the first release featuring an experimental build mode that disables the GIL, opening the door to significant performance improvements for multi-threaded applications.

Before discussing these new changes, let’s do a quick recap of the pros and cons of the GIL.

Advantages and Disadvantages of the GIL

The GIL offers several advantages, including:

  • Simplified Memory Management: By blocking threads from concurrently modifying an object’s reference count, the GIL prevents race conditions and ensures the garbage collector doesn’t free an object while it’s still referenced, making the core implementation simpler and more robust.
  • Ease of C Extension Integration: Many C libraries and extensions, which are not inherently thread-safe, can be used safely under the GIL’s protection.
  • Single-thread Performance: Since CPython uses reference counting for memory management, the garbage collector has minimal impact compared to mark-and-sweep algorithms used in languages - ex: Java -, which can be unpredictable and cause pauses.

But if only one thread can use the interpreter at a given time, how does Python run multiple threads?

Thread switching is a complex topic; even the interpreter doesn’t always decide which thread runs next, as the operating system also influences scheduling. In this blog, we will focus solely on the Python side of thread management.

what happens when a thread holds the GIL?

When a thread holds the GIL, it may encounter one of the following scenarios:

  • If it reaches an I/O task, the thread willingly releases the lock while waiting, allowing other threads to run.
  • During CPU-intensive operations, the lock is automatically released after a defined timeout (typically 5ms).

Now that we understand how thread switching works, it’s clear that one of the main downsides of the GIL is limited true multi-threaded parallelism in CPU-bound programs. Removing the GIL requires a solution that grants safe, concurrent access to objects and memory management without compromising single-thread performance.

PEP 703

Sam Gross, the author of PEP 703, has proposed an ingenious solution to remove the GIL from CPython while ensuring thread safety. The proposal rethinks CPython’s memory management and introduces three key techniques:

  • Biased Reference Counting:
    Many objects are primarily modified by a single thread, so biased reference counting lets that thread update an object’s reference count without atomic overhead. If another thread intervenes, the system safely falls back to slower, thread-safe operations—optimizing the common single-threaded case.

  • Deferred Reference Counting:
    For objects like top-level functions, code objects, modules, and methods that are accessed concurrently, deferred reference counting postpones immediate atomic updates. It batches reference count changes to reduce contention, thereby minimizing overhead for dynamic objects that aren’t immortal.

  • Immortal Objects:
    Certain objects—such as interned strings, small integers, PyTypeObjects, and the True, False, and None—live for the duration of the program. These are marked as immortal (their reference count is set to UINT32_MAX), so Py_INCREF and Py_DECREF become no-ops, avoiding contention when accessed by multiple threads.

Together, these techniques pave the way for enhanced parallelism and a more efficient, concurrent CPython.

Performance

check Rostyslav Bilan's great blog for more details about the benchmark below.

Image description

PEP 703 Adoption

From the Steering Council’s notice about the adoption of PEP 703:

  • Short term: We will add the no-GIL build as an experimental build mode, presumably in Python 3.13 (if it slips to 3.14, that is not a problem). The build mode is experimental to clarify that, although the core developers support it, we cannot expect the community to adopt it immediately. We need time to determine the necessary changes in API design, packaging, and distribution, and we want to discourage distributors from shipping the experimental no-GIL build as the default interpreter.
  • Mid-term: Once there is sufficient community support for production use of no-GIL, we will support the no-GIL build—but not as the default—while setting a target date or Python version for making it the default. This timing will depend on factors such as backward compatibility of API changes (e.g., the stable ABI) and the remaining work identified by the community. We expect this phase to take at least a year or two, possibly more. Some distributors may start shipping no-GIL by default, though this will vary with package support.
  • Long-term: Our goal is for no-GIL to become the default, eventually removing any vestiges of the GIL without unnecessarily breaking backward compatibility. We do not want to maintain two common build modes indefinitely, as this can double testing resources and debugging efforts. However, the transition cannot be rushed and may take as much as five years.

conclusion

The move to remove the GIL marks a major evolution in CPython’s design. By rethinking memory management with biased and deferred reference counting, plus immortal objects, PEP 703 offers a practical path to better multi-threaded performance without losing single-thread efficiency. As Python gradually shifts toward a no-GIL future, we can look forward to an interpreter that’s more capable on today’s multi-core systems.

Resources

https://peps.python.org/pep-0703/
https://www.youtube.com/watch?v=Obt-vMVdM8s
https://lwn.net/Articles/940780/

Top comments (0)