支援自由執行緒的 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 likePySequence_Fast_GET_SIZEthat 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 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 |
|---|---|