C API Extension Support for Free Threading

Starting with the 3.13 release, CPython has support for running withtheglobal interpreter lock (GIL) disabled in a configurationcalledfree threading. This document describes how to adapt C APIextensions to support free threading.

Identifying the Free-Threaded Build in C

The CPython C API exposes thePy_GIL_DISABLED macro: in the free-threadedbuild it’s defined to1, and in the regular build it’s not defined.You can use it to enable code that only runs under the free-threaded build:

#ifdef Py_GIL_DISABLED/* code that only runs in the free-threaded build */#endif

Note

On Windows, this macro is not defined automatically, but must be specifiedto the compiler when building. Thesysconfig.get_config_var() functioncan be used to determine whether the current running interpreter had themacro defined.

Module Initialization

Extension modules need to explicitly indicate that they support running withthe GIL disabled; otherwise importing the extension will raise a warning andenable the GIL at runtime.

There are two ways to indicate that an extension module supports running withthe GIL disabled depending on whether the extension uses multi-phase orsingle-phase initialization.

Multi-Phase Initialization

Extensions that use multi-phase initialization (i.e.,PyModuleDef_Init()) should add aPy_mod_gil slot in themodule definition. If your extension supports older versions of CPython,you should guard the slot with aPY_VERSION_HEX check.

staticstructPyModuleDef_Slotmodule_slots[]={...#if PY_VERSION_HEX >= 0x030D0000{Py_mod_gil,Py_MOD_GIL_NOT_USED},#endif{0,NULL}};staticstructPyModuleDefmoduledef={PyModuleDef_HEAD_INIT,.m_slots=module_slots,...};

Single-Phase Initialization

Extensions that use single-phase initialization (i.e.,PyModule_Create()) should callPyUnstable_Module_SetGIL() toindicate that they support running with the GIL disabled. The function isonly defined in the free-threaded build, so you should guard the call with#ifdefPy_GIL_DISABLED to avoid compilation errors in the regular build.

staticstructPyModuleDefmoduledef={PyModuleDef_HEAD_INIT,...};PyMODINIT_FUNCPyInit_mymodule(void){PyObject*m=PyModule_Create(&moduledef);if(m==NULL){returnNULL;}#ifdef Py_GIL_DISABLEDPyUnstable_Module_SetGIL(m,Py_MOD_GIL_NOT_USED);#endifreturnm;}

General API Guidelines

Most of the C API is thread-safe, but there are some exceptions.

  • Struct Fields: Accessing fields in Python C API objects or structsdirectly is not thread-safe if the field may be concurrently modified.

  • Macros: Accessor macros likePyList_GET_ITEM,PyList_SET_ITEM, and macros likePySequence_Fast_GET_SIZE that use the object returned byPySequence_Fast() do not perform any error checking or locking.These macros are not thread-safe if the container object may be modifiedconcurrently.

  • Borrowed References: C API functions that returnborrowed references may not be thread-safe ifthe containing object is modified concurrently. See the section onborrowed references for more information.

Container Thread Safety

Containers likePyListObject,PyDictObject, andPySetObject perform internal lockingin the free-threaded build. For example, thePyList_Append() willlock the list before appending an item.

PyDict_Next

A notable exception isPyDict_Next(), which does not lock thedictionary. You should usePy_BEGIN_CRITICAL_SECTION to protectthe dictionary while iterating over it if the dictionary may be concurrentlymodified:

Py_BEGIN_CRITICAL_SECTION(dict);PyObject*key,*value;Py_ssize_tpos=0;while(PyDict_Next(dict,&pos,&key,&value)){...}Py_END_CRITICAL_SECTION();

Borrowed References

Some C API functions returnborrowed references.These APIs are not thread-safe if the containing object is modifiedconcurrently. For example, it’s not safe to usePyList_GetItem()if the list may be modified concurrently.

The following table lists some borrowed reference APIs and their replacementsthat returnstrong references.

Borrowed reference API

Strong reference API

PyList_GetItem()

PyList_GetItemRef()

PyList_GET_ITEM()

PyList_GetItemRef()

PyDict_GetItem()

PyDict_GetItemRef()

PyDict_GetItemWithError()

PyDict_GetItemRef()

PyDict_GetItemString()

PyDict_GetItemStringRef()

PyDict_SetDefault()

PyDict_SetDefaultRef()

PyDict_Next()

none (seePyDict_Next)

PyWeakref_GetObject()

PyWeakref_GetRef()

PyWeakref_GET_OBJECT()

PyWeakref_GetRef()

PyImport_AddModule()

PyImport_AddModuleRef()

PyCell_GET()

PyCell_Get()

Not all APIs that return borrowed references are problematic. Forexample,PyTuple_GetItem() is safe because tuples are immutable.Similarly, not all uses of the above APIs are problematic. For example,PyDict_GetItem() is often used for parsing keyword argumentdictionaries in function calls; those keyword argument dictionaries areeffectively private (not accessible by other threads), so using borrowedreferences in that context is safe.

Some of these functions were added in Python 3.13. You can use thepythoncapi-compat packageto provide implementations of these functions for older Python versions.

Memory Allocation APIs

Python’s memory management C API provides functions in three differentallocation domains: “raw”, “mem”, and “object”.For thread-safety, the free-threaded build requires that only Python objectsare allocated using the object domain, and that all Python objects areallocated using that domain. This differs from the prior Python versions,where this was only a best practice and not a hard requirement.

Note

Search for uses ofPyObject_Malloc() in yourextension and check that the allocated memory is used for Python objects.UsePyMem_Malloc() to allocate buffers instead ofPyObject_Malloc().

Thread State and GIL APIs

Python provides a set of functions and macros to manage thread state and theGIL, such as:

These functions should still be used in the free-threaded build to managethread state even when theGIL is disabled. For example, if youcreate a thread outside of Python, you must callPyGILState_Ensure()before calling into the Python API to ensure that the thread has a validPython thread state.

You should continue to callPyEval_SaveThread() orPy_BEGIN_ALLOW_THREADS around blocking operations, such as I/O orlock acquisitions, to allow other threads to run thecyclic garbage collector.

Protecting Internal Extension State

Your extension may have internal state that was previously protected by theGIL. You may need to add locking to protect this state. The approach willdepend on your extension, but some common patterns include:

  • Caches: global caches are a common source of shared state. Considerusing a lock to protect the cache or disabling it in the free-threaded buildif the cache is not critical for performance.

  • Global State: global state may need to be protected by a lock or movedto thread local storage. C11 and C++11 provide thethread_local or_Thread_local forthread-local storage.

Critical Sections

In the free-threaded build, CPython provides a mechanism called “criticalsections” to protect data that would otherwise be protected by the GIL.While extension authors may not interact with the internal critical sectionimplementation directly, understanding their behavior is crucial when usingcertain C API functions or managing shared state in the free-threaded build.

What Are Critical Sections?

Conceptually, critical sections act as a deadlock avoidance layer built ontop of simple mutexes. Each thread maintains a stack of active criticalsections. When a thread needs to acquire a lock associated with a criticalsection (e.g., implicitly when calling a thread-safe C API function likePyDict_SetItem(), or explicitly using macros), it attempts to acquirethe underlying mutex.

Using Critical Sections

The primary APIs for using critical sections are:

These macros must be used in matching pairs and must appear in the same Cscope, since they establish a new local scope. These macros are no-ops innon-free-threaded builds, so they can be safely added to code that needs tosupport both build types.

A common use of a critical section would be to lock an object while accessingan internal attribute of it. For example, if an extension type has an internalcount field, you could use a critical section while reading or writing thatfield:

// read the count, returns new reference to internal count valuePyObject*result;Py_BEGIN_CRITICAL_SECTION(obj);result=Py_NewRef(obj->count);Py_END_CRITICAL_SECTION();returnresult;// write the count, consumes reference from new_countPy_BEGIN_CRITICAL_SECTION(obj);obj->count=new_count;Py_END_CRITICAL_SECTION();

How Critical Sections Work

Unlike traditional locks, critical sections do not guarantee exclusive accessthroughout their entire duration. If a thread would block while holding acritical section (e.g., by acquiring another lock or performing I/O), thecritical section is temporarily suspended—all locks are released—and thenresumed when the blocking operation completes.

This behavior is similar to what happens with the GIL when a thread makes ablocking call. The key differences are:

  • Critical sections operate on a per-object basis rather than globally

  • Critical sections follow a stack discipline within each thread (the “begin” and“end” macros enforce this since they must be paired and within the same scope)

  • Critical sections automatically release and reacquire locks around potentialblocking operations

Deadlock Avoidance

Critical sections help avoid deadlocks in two ways:

  1. If a thread tries to acquire a lock that’s already held by another thread,it first suspends all of its active critical sections, temporarily releasingtheir locks

  2. When the blocking operation completes, only the top-most critical section isreacquired first

This means you cannot rely on nested critical sections to lock multiple objectsat once, as the inner critical section may suspend the outer ones. Instead, usePy_BEGIN_CRITICAL_SECTION2 to lock two objects simultaneously.

Note that the locks described above are onlyPyMutex based locks.The critical section implementation does not know about or affect other lockingmechanisms that might be in use, like POSIX mutexes. Also note that whileblocking on anyPyMutex causes the critical sections to besuspended, only the mutexes that are part of the critical sections arereleased. IfPyMutex is used without a critical section, it willnot be released and therefore does not get the same deadlock avoidance.

Important Considerations

  • Critical sections may temporarily release their locks, allowing other threadsto modify the protected data. Be careful about making assumptions about thestate of the data after operations that might block.

  • Because locks can be temporarily released (suspended), entering a criticalsection does not guarantee exclusive access to the protected resourcethroughout the section’s duration. If code within a critical section callsanother function that blocks (e.g., acquires another lock, performs blockingI/O), all locks held by the thread via critical sections will be released.This is similar to how the GIL can be released during blocking calls.

  • Only the lock(s) associated with the most recently entered (top-most)critical section are guaranteed to be held at any given time. Locks forouter, nested critical sections might have been suspended.

  • You can lock at most two objects simultaneously with these APIs. If you needto lock more objects, you’ll need to restructure your code.

  • While critical sections will not deadlock if you attempt to lock the sameobject twice, they are less efficient than purpose-built reentrant locks forthis use case.

  • When usingPy_BEGIN_CRITICAL_SECTION2, the order of the objectsdoesn’t affect correctness (the implementation handles deadlock avoidance),but it’s good practice to always lock objects in a consistent order.

  • Remember that the critical section macros are primarily for protecting accesstoPython objects that might be involved in internal CPython operationssusceptible to the deadlock scenarios described above. For protecting purelyinternal extension state, standard mutexes or other synchronizationprimitives might be more appropriate.

Building Extensions for the Free-Threaded Build

C API extensions need to be built specifically for the free-threaded build.The wheels, shared libraries, and binaries are indicated by at suffix.

Limited C API and Stable ABI

The free-threaded build does not currently support theLimited C API or the stable ABI. If you usesetuptools to buildyour extension and currently setpy_limited_api=True you can usepy_limited_api=notsysconfig.get_config_var("Py_GIL_DISABLED") to opt outof the limited API when building with the free-threaded build.

Note

You will need to build separate wheels specifically for the free-threadedbuild. If you currently use the stable ABI, you can continue to build asingle wheel for multiple non-free-threaded Python versions.

Windows

Due to a limitation of the official Windows installer, you will need tomanually definePy_GIL_DISABLED=1 when building extensions from source.

See also

Porting Extension Modules to Support Free-Threading:A community-maintained porting guide for extension authors.