支援自由執行緒的 C API 擴充

Starting with the 3.13 release, CPython has support for running with the global interpreter lock (GIL) disabled in a configuration called free threading. This document describes how to adapt C API extensions to support free threading.

Identifying the Free-Threaded Build in C

The CPython C API exposes the Py_GIL_DISABLED macro: in the free-threaded build it's defined to 1, 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

備註

On Windows, this macro is not defined automatically, but must be specified to the compiler when building. The sysconfig.get_config_var() function can be used to determine whether the current running interpreter had the macro defined.

模組初始化

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

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

Multi-Phase Initialization

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

static struct PyModuleDef_Slot module_slots[] = {
    ...
#if PY_VERSION_HEX >= 0x030D0000
    {Py_mod_gil, Py_MOD_GIL_NOT_USED},
#endif
    {0, NULL}
};

static struct PyModuleDef moduledef = {
    PyModuleDef_HEAD_INIT,
    .m_slots = module_slots,
    ...
};

單一階段初始化 (Single-Phase Initialization)

Extensions that use single-phase initialization (i.e., PyModule_Create()) should call PyUnstable_Module_SetGIL() to indicate that they support running with the GIL disabled. The function is only defined in the free-threaded build, so you should guard the call with #ifdef Py_GIL_DISABLED to avoid compilation errors in the regular build.

static struct PyModuleDef moduledef = {
    PyModuleDef_HEAD_INIT,
    ...
};

PyMODINIT_FUNC
PyInit_mymodule(void)
{
    PyObject *m = PyModule_Create(&moduledef);
    if (m == NULL) {
        return NULL;
    }
#ifdef Py_GIL_DISABLED
    PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
#endif
    return m;
}

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 structs directly is not thread-safe if the field may be concurrently modified.

  • Macros: Accessor macros like PyList_GET_ITEM, PyList_SET_ITEM, and macros like PySequence_Fast_GET_SIZE that use the object returned by PySequence_Fast() do not perform any error checking or locking. These macros are not thread-safe if the container object may be modified concurrently.

  • Borrowed References: C API functions that return borrowed references may not be thread-safe if the containing object is modified concurrently. See the section on borrowed references for more information.

容器執行緒安全性

Containers like PyListObject, PyDictObject, and PySetObject perform internal locking in the free-threaded build. For example, the PyList_Append() will lock the list before appending an item.

PyDict_Next

A notable exception is PyDict_Next(), which does not lock the dictionary. You should use Py_BEGIN_CRITICAL_SECTION to protect the dictionary while iterating over it if the dictionary may be concurrently modified:

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

借用參照

Some C API functions return borrowed references. These APIs are not thread-safe if the containing object is modified concurrently. For example, it's not safe to use PyList_GetItem() if the list may be modified concurrently.

The following table lists some borrowed reference APIs and their replacements that return strong references.

借用參照 API

強參照 API

PyList_GetItem()

PyList_GetItemRef()

PyList_GET_ITEM()

PyList_GetItemRef()

PyDict_GetItem()

PyDict_GetItemRef()

PyDict_GetItemWithError()

PyDict_GetItemRef()

PyDict_GetItemString()

PyDict_GetItemStringRef()