PEP 779 makes free-threaded Python a supported build option
Acceptance of PEP 779 moves free-threaded Python 3.14 beyond experimental status. The decision creates a support commitment while leaving the default build, Stable ABI work, and broader adoption as separate questions.
Removing a global lock changes more than the number of threads that can execute Python code. It changes assumptions in reference counting, object access, and extension modules. A project can demonstrate that such a build works before it is ready to promise that the ecosystem can depend on it. PEP 779 defines the transition between those stages for CPython.
The PEP 779 resolution dated June 16 approves supported status for the free-threaded build of Python 3.14. The build remains optional. This is the second phase of the rollout established around PEP 703, following an experimental phase in Python 3.13. Making free threading the default belongs to a later decision.
I use Python because its libraries often solve difficult problems before application code needs to address them. That makes extension compatibility central to my interest in free threading. A runtime capability matters much more when the libraries that justify using the language can depend on its interfaces.
Supported status is a maintenance commitment
PEP 779 describes supported status as a signal that the design and APIs are usable and stable enough to carry an ongoing support obligation. The proposal also acknowledges that more ecosystem adoption is necessary before the project can judge the full costs and benefits. Supported status gives library maintainers a firmer basis for doing that work.
The Steering Council acceptance removes the experimental designation while retaining further requirements for the supported phase. Its practical commitment is that the option will not disappear without a proper deprecation schedule. The council recommends reflecting the status change in the third beta, with the release manager's approval.
That does not imply that every extension is ready, nor that all application code benefits from enabling more simultaneous execution. The decision changes the project's responsibility for maintaining the build. Package compatibility and application architecture remain work that has to happen around it.
The distinction is useful for investment decisions. An experimental interpreter build can be an interesting target for investigation. A supported option provides a stronger reason for an extension maintainer to allocate ongoing effort to compatible releases. That effort, in turn, supplies evidence for deciding whether the default should eventually change.
The GIL also protected interpreter state
PEP 703 explains why removing the Global Interpreter Lock requires changes throughout CPython. The GIL prevents multiple threads from executing Python code at the same time. It also supplies exclusion that internal object operations have historically relied upon.
Consider reference counting. With one thread executing protected interpreter operations at a time, an increment can use a simpler update. Removing that exclusion makes concurrent changes to the same count possible. Making every update atomic would address one correctness problem while adding overhead to operations throughout the interpreter.
PEP 703 instead combines techniques. Biased reference counting gives an object an owning thread. Updates from that thread use a local count, while other threads update a shared count through atomic operations. The scheme aims to keep the common case inexpensive when an object stays within one thread.
Frequently shared objects create another problem because contention on their reference counts can limit scaling. Immortal objects avoid ordinary reference-count changes. Deferred reference counting avoids selected updates and relies on garbage collection to account for references that the immediate count does not include.
Those mechanisms explain why free threading has a performance and memory cost even for code that does not exploit parallel execution. Additional synchronization and bookkeeping are part of making shared objects safe. Removing the global lock requires replacing the protections that it supplied.
Object safety differs from application correctness
Containers require their own synchronization. PEP 703 proposes lightweight locks associated with individual lists, dictionaries, and sets. Operations that mutate a container acquire its lock, and many reads also need protection. A small number of read paths use optimistic access with checks and a locked fallback.
These protections keep internal state coherent. They do not turn an arbitrary sequence of Python statements into a transaction. An application that reads some state, makes a decision, and updates it still needs a way to coordinate the entire operation when other threads can interfere.
Even the GIL did not make all compound behavior atomic. PEP 703 points out that operations involving user-defined iteration or equality can execute other Python code or release the lock internally. Free threading makes it especially important to state which invariant the application needs, rather than relying on a vague idea that a container is thread-safe.
An optimistic list read also shows why allocator behavior becomes relevant. Loading an element and obtaining an owned reference are separate steps. Another thread can change the container between them. The runtime must ensure that the memory needed to validate and retain the reference remains usable during that interval.
The PEP coordinates these reads with modified mimalloc allocation behavior and delayed reuse rules. This is implementation complexity that ordinary Python code need not reproduce. It still matters to the overall tradeoff because faster concurrent reads depend on more than removing a mutex around the interpreter.
Extensions have a lifetime problem to solve
The same issue appears at the C interface when code retrieves a borrowed reference and then increments its reference count. Under the GIL, the caller could rely on exclusion across those steps. Without it, another thread may remove the object from its container before the caller establishes ownership.
PEP 703 describes new-reference alternatives for problematic borrowed-reference APIs. The distinction concerns the lifetime of the returned object, not merely whether an individual lookup protects a container internally. Extension code has to hold a valid reference while it uses the object.
Immutable containers avoid some of these particular problems, and the proposal explicitly notes that not every borrowed reference is unsafe. An argument dictionary private to a function call differs from a shared dictionary that another thread can mutate. Correct migration therefore requires examining ownership and access patterns.
This is why a broad claim that C extensions already run outside the GIL misses much of the work. A native routine may release the GIL while it computes over its own data, then reacquire it before touching Python objects. Allowing multiple threads to access those Python objects concurrently requires a different set of guarantees.
My view is that the supported build is valuable because it gives that work a stable target. The native library ecosystem is a principal reason to choose Python. An interpreter transition that treats those libraries as part of the engineering problem is more useful than treating parallel execution as an isolated interpreter feature.
Performance costs need an explicit budget
PEP 779 discusses overhead in both CPU time and memory. It reports measurements through the pyperformance benchmark suite and distinguishes those aggregate observations from the benefits available in applications designed to exploit threads. A single-threaded cost can coexist with a multithreaded benefit.
The proposal suggests limits for entering the supported phase, including a performance target and a memory-use target. The acceptance adds council oversight around these costs and requires repeatable evidence. This is a governance mechanism for preventing a new execution mode from imposing costs without an explicit decision.
These budgets should not be interpreted as predicted speedups for an application. A benchmark suite summarizes selected programs under particular configurations. An application dominated by one shared data structure has different limits from work split among threads that mostly use independent objects.
Memory cost is also substantive. Extra bookkeeping and synchronization have to exist somewhere. The supported phase provides a period in which the community can decide whether those costs are appropriate across actual workloads, while the conventional build remains available.
A separate decision remains for the default
Stable ABI support is an unresolved concern in the PEP. A stable binary interface would simplify distribution for extension authors, but committing too early could preserve mistakes that cannot easily be removed. Adopting libraries provide feedback that helps determine whether the interface is sufficient.
That creates a dependency between support and experience. Packages need a credible runtime target before investing in it, while the runtime needs package experience before freezing more of its interface. Phase II gives both sides a way to proceed without claiming that every distribution problem has already been solved.
The default-build question has a broader effect because it changes the environment ordinary users receive. PEP 779 leaves that question to future work and explicitly ties it to community willingness and demonstrated benefit. Today's decision does not schedule that transition.
The immediate result is a supported optional build for Python 3.14 and a defined period of ecosystem development around it. Extension maintainers can work against an option with a continuing support commitment. Their results will help establish whether the APIs, distribution model, and application benefits justify changing the default later.