mirror of
https://github.com/python/cpython.git
synced 2024-11-24 17:47:13 +01:00
e6bb1a2b28
* gh-119802: Update memory management docs for free-threaded build * nit * nit * Address code review * nit * Update Doc/c-api/memory.rst Co-authored-by: Sam Gross <colesbury@gmail.com> --------- Co-authored-by: Sam Gross <colesbury@gmail.com>
281 lines
11 KiB
ReStructuredText
281 lines
11 KiB
ReStructuredText
.. highlight:: c
|
|
|
|
.. _freethreading-extensions-howto:
|
|
|
|
******************************************
|
|
C API Extension Support for Free Threading
|
|
******************************************
|
|
|
|
Starting with the 3.13 release, CPython has experimental support for running
|
|
with the :term:`global interpreter lock` (GIL) disabled in a configuration
|
|
called :term:`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
|
|
|
|
Module Initialization
|
|
=====================
|
|
|
|
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.,
|
|
:c:func:`PyModuleDef_Init`) should add a :c:data:`Py_mod_gil` slot in the
|
|
module definition. If your extension supports older versions of CPython,
|
|
you should guard the slot with a :c:data:`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.,
|
|
:c:func:`PyModule_Create`) should call :c:func:`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 :c:macro:`PyList_GET_ITEM` and
|
|
:c:macro:`PyList_SET_ITEM` 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
|
|
:term:`borrowed references <borrowed reference>` may not be thread-safe if
|
|
the containing object is modified concurrently. See the section on
|
|
:ref:`borrowed references <borrowed-references>` for more information.
|
|
|
|
|
|
Container Thread Safety
|
|
.......................
|
|
|
|
Containers like :c:struct:`PyListObject`,
|
|
:c:struct:`PyDictObject`, and :c:struct:`PySetObject` perform internal locking
|
|
in the free-threaded build. For example, the :c:func:`PyList_Append` will
|
|
lock the list before appending an item.
|
|
|
|
.. _PyDict_Next:
|
|
|
|
``PyDict_Next``
|
|
'''''''''''''''
|
|
|
|
A notable exception is :c:func:`PyDict_Next`, which does not lock the
|
|
dictionary. You should use :c:macro:`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();
|
|
|
|
|
|
Borrowed References
|
|
===================
|
|
|
|
.. _borrowed-references:
|
|
|
|
Some C API functions return :term:`borrowed references <borrowed reference>`.
|
|
These APIs are not thread-safe if the containing object is modified
|
|
concurrently. For example, it's not safe to use :c:func:`PyList_GetItem`
|
|
if the list may be modified concurrently.
|
|
|
|
The following table lists some borrowed reference APIs and their replacements
|
|
that return :term:`strong references <strong reference>`.
|
|
|
|
+-----------------------------------+-----------------------------------+
|
|
| Borrowed reference API | Strong reference API |
|
|
+===================================+===================================+
|
|
| :c:func:`PyList_GetItem` | :c:func:`PyList_GetItemRef` |
|
|
+-----------------------------------+-----------------------------------+
|
|
| :c:func:`PyDict_GetItem` | :c:func:`PyDict_GetItemRef` |
|
|
+-----------------------------------+-----------------------------------+
|
|
| :c:func:`PyDict_GetItemWithError` | :c:func:`PyDict_GetItemRef` |
|
|
+-----------------------------------+-----------------------------------+
|
|
| :c:func:`PyDict_GetItemString` | :c:func:`PyDict_GetItemStringRef` |
|
|
+-----------------------------------+-----------------------------------+
|
|
| :c:func:`PyDict_SetDefault` | :c:func:`PyDict_SetDefaultRef` |
|
|
+-----------------------------------+-----------------------------------+
|
|
| :c:func:`PyDict_Next` | none (see :ref:`PyDict_Next`) |
|
|
+-----------------------------------+-----------------------------------+
|
|
| :c:func:`PyWeakref_GetObject` | :c:func:`PyWeakref_GetRef` |
|
|
+-----------------------------------+-----------------------------------+
|
|
| :c:func:`PyWeakref_GET_OBJECT` | :c:func:`PyWeakref_GetRef` |
|
|
+-----------------------------------+-----------------------------------+
|
|
| :c:func:`PyImport_AddModule` | :c:func:`PyImport_AddModuleRef` |
|
|
+-----------------------------------+-----------------------------------+
|
|
|
|
Not all APIs that return borrowed references are problematic. For
|
|
example, :c:func:`PyTuple_GetItem` is safe because tuples are immutable.
|
|
Similarly, not all uses of the above APIs are problematic. For example,
|
|
:c:func:`PyDict_GetItem` is often used for parsing keyword argument
|
|
dictionaries in function calls; those keyword argument dictionaries are
|
|
effectively private (not accessible by other threads), so using borrowed
|
|
references in that context is safe.
|
|
|
|
Some of these functions were added in Python 3.13. You can use the
|
|
`pythoncapi-compat <https://github.com/python/pythoncapi-compat>`_ package
|
|
to provide implementations of these functions for older Python versions.
|
|
|
|
|
|
.. _free-threaded-memory-allocation:
|
|
|
|
Memory Allocation APIs
|
|
======================
|
|
|
|
Python's memory management C API provides functions in three different
|
|
:ref:`allocation domains <allocator-domains>`: "raw", "mem", and "object".
|
|
For thread-safety, the free-threaded build requires that only Python objects
|
|
are allocated using the object domain, and that all Python object are
|
|
allocated 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 of :c:func:`PyObject_Malloc` in your
|
|
extension and check that the allocated memory is used for Python objects.
|
|
Use :c:func:`PyMem_Malloc` to allocate buffers instead of
|
|
:c:func:`PyObject_Malloc`.
|
|
|
|
|
|
Thread State and GIL APIs
|
|
=========================
|
|
|
|
Python provides a set of functions and macros to manage thread state and the
|
|
GIL, such as:
|
|
|
|
* :c:func:`PyGILState_Ensure` and :c:func:`PyGILState_Release`
|
|
* :c:func:`PyEval_SaveThread` and :c:func:`PyEval_RestoreThread`
|
|
* :c:macro:`Py_BEGIN_ALLOW_THREADS` and :c:macro:`Py_END_ALLOW_THREADS`
|
|
|
|
These functions should still be used in the free-threaded build to manage
|
|
thread state even when the :term:`GIL` is disabled. For example, if you
|
|
create a thread outside of Python, you must call :c:func:`PyGILState_Ensure`
|
|
before calling into the Python API to ensure that the thread has a valid
|
|
Python thread state.
|
|
|
|
You should continue to call :c:func:`PyEval_SaveThread` or
|
|
:c:macro:`Py_BEGIN_ALLOW_THREADS` around blocking operations, such as I/O or
|
|
lock acquisitions, to allow other threads to run the
|
|
:term:`cyclic garbage collector <garbage collection>`.
|
|
|
|
|
|
Protecting Internal Extension State
|
|
===================================
|
|
|
|
Your extension may have internal state that was previously protected by the
|
|
GIL. You may need to add locking to protect this state. The approach will
|
|
depend on your extension, but some common patterns include:
|
|
|
|
* **Caches**: global caches are a common source of shared state. Consider
|
|
using a lock to protect the cache or disabling it in the free-threaded build
|
|
if the cache is not critical for performance.
|
|
* **Global State**: global state may need to be protected by a lock or moved
|
|
to thread local storage. C11 and C++11 provide the ``thread_local`` or
|
|
``_Thread_local`` for
|
|
`thread-local storage <https://en.cppreference.com/w/c/language/storage_duration>`_.
|
|
|
|
|
|
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 a ``t`` suffix.
|
|
|
|
* `pypa/manylinux <https://github.com/pypa/manylinux>`_ supports the
|
|
free-threaded build, with the ``t`` suffix, such as ``python3.13t``.
|
|
* `pypa/cibuildwheel <https://github.com/pypa/cibuildwheel>`_ supports the
|
|
free-threaded build if you set
|
|
`CIBW_FREE_THREADED_SUPPORT <https://cibuildwheel.pypa.io/en/stable/options/#free-threaded-support>`_.
|
|
|
|
Limited C API and Stable ABI
|
|
............................
|
|
|
|
The free-threaded build does not currently support the
|
|
:ref:`Limited C API <limited-c-api>` or the stable ABI. If you use
|
|
`setuptools <https://setuptools.pypa.io/en/latest/setuptools.html>`_ to build
|
|
your extension and currently set ``py_limited_api=True`` you can use
|
|
``py_limited_api=not sysconfig.get_config_var("Py_GIL_DISABLED")`` to opt out
|
|
of the limited API when building with the free-threaded build.
|
|
|
|
.. note::
|
|
You will need to build separate wheels specifically for the free-threaded
|
|
build. If you currently use the stable ABI, you can continue to build a
|
|
single wheel for multiple non-free-threaded Python versions.
|
|
|
|
|
|
Windows
|
|
.......
|
|
|
|
Due to a limitation of the official Windows installer, you will need to
|
|
manually define ``Py_GIL_DISABLED=1`` when building extensions from source.
|
|
|
|
.. seealso::
|
|
|
|
`Porting Extension Modules to Support Free-Threading
|
|
<https://py-free-threading.github.io/porting/>`_:
|
|
A community-maintained porting guide for extension authors.
|