Skip to content

Commit 02b272b

Browse files
colesburyngoldbaum
andauthored
gh-119241: Add HOWTO for free-threaded C API extensions (#119877)
Some sections adapted from https://github.com/Quansight-Labs/free-threaded-compatibility/ written by Nathan Goldbaum. Co-authored-by: Nathan Goldbaum <[email protected]>
1 parent dacc5ac commit 02b272b

File tree

2 files changed

+255
-0
lines changed

2 files changed

+255
-0
lines changed
+254
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
.. highlight:: c
2+
3+
.. _freethreading-extensions-howto:
4+
5+
******************************************
6+
C API Extension Support for Free Threading
7+
******************************************
8+
9+
Starting with the 3.13 release, CPython has experimental support for running
10+
with the :term:`global interpreter lock` (GIL) disabled in a configuration
11+
called :term:`free threading`. This document describes how to adapt C API
12+
extensions to support free threading.
13+
14+
15+
Identifying the Free-Threaded Build in C
16+
========================================
17+
18+
The CPython C API exposes the ``Py_GIL_DISABLED`` macro: in the free-threaded
19+
build it's defined to ``1``, and in the regular build it's not defined.
20+
You can use it to enable code that only runs under the free-threaded build::
21+
22+
#ifdef Py_GIL_DISABLED
23+
/* code that only runs in the free-threaded build */
24+
#endif
25+
26+
Module Initialization
27+
=====================
28+
29+
Extension modules need to explicitly indicate that they support running with
30+
the GIL disabled; otherwise importing the extension will raise a warning and
31+
enable the GIL at runtime.
32+
33+
There are two ways to indicate that an extension module supports running with
34+
the GIL disabled depending on whether the extension uses multi-phase or
35+
single-phase initialization.
36+
37+
Multi-Phase Initialization
38+
..........................
39+
40+
Extensions that use multi-phase initialization (i.e.,
41+
:c:func:`PyModuleDef_Init`) should add a :c:data:`Py_mod_gil` slot in the
42+
module definition. If your extension supports older versions of CPython,
43+
you should guard the slot with a :c:data:`PY_VERSION_HEX` check.
44+
45+
::
46+
47+
static struct PyModuleDef_Slot module_slots[] = {
48+
...
49+
#if PY_VERSION_HEX >= 0x030D0000
50+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
51+
#endif
52+
{0, NULL}
53+
};
54+
55+
static struct PyModuleDef moduledef = {
56+
PyModuleDef_HEAD_INIT,
57+
.m_slots = module_slots,
58+
...
59+
};
60+
61+
62+
Single-Phase Initialization
63+
...........................
64+
65+
Extensions that use single-phase initialization (i.e.,
66+
:c:func:`PyModule_Create`) should call :c:func:`PyUnstable_Module_SetGIL` to
67+
indicate that they support running with the GIL disabled. The function is
68+
only defined in the free-threaded build, so you should guard the call with
69+
``#ifdef Py_GIL_DISABLED`` to avoid compilation errors in the regular build.
70+
71+
::
72+
73+
static struct PyModuleDef moduledef = {
74+
PyModuleDef_HEAD_INIT,
75+
...
76+
};
77+
78+
PyMODINIT_FUNC
79+
PyInit_mymodule(void)
80+
{
81+
PyObject *m = PyModule_Create(&moduledef);
82+
if (m == NULL) {
83+
return NULL;
84+
}
85+
#ifdef Py_GIL_DISABLED
86+
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
87+
#endif
88+
return m;
89+
}
90+
91+
92+
General API Guidelines
93+
======================
94+
95+
Most of the C API is thread-safe, but there are some exceptions.
96+
97+
* **Struct Fields**: Accessing fields in Python C API objects or structs
98+
directly is not thread-safe if the field may be concurrently modified.
99+
* **Macros**: Accessor macros like :c:macro:`PyList_GET_ITEM` and
100+
:c:macro:`PyList_SET_ITEM` do not perform any error checking or locking.
101+
These macros are not thread-safe if the container object may be modified
102+
concurrently.
103+
* **Borrowed References**: C API functions that return
104+
:term:`borrowed references <borrowed reference>` may not be thread-safe if
105+
the containing object is modified concurrently. See the section on
106+
:ref:`borrowed references <borrowed-references>` for more information.
107+
108+
109+
Container Thread Safety
110+
.......................
111+
112+
Containers like :c:struct:`PyListObject`,
113+
:c:struct:`PyDictObject`, and :c:struct:`PySetObject` perform internal locking
114+
in the free-threaded build. For example, the :c:func:`PyList_Append` will
115+
lock the list before appending an item.
116+
117+
118+
Borrowed References
119+
===================
120+
121+
.. _borrowed-references:
122+
123+
Some C API functions return :term:`borrowed references <borrowed reference>`.
124+
These APIs are not thread-safe if the containing object is modified
125+
concurrently. For example, it's not safe to use :c:func:`PyList_GetItem`
126+
if the list may be modified concurrently.
127+
128+
The following table lists some borrowed reference APIs and their replacements
129+
that return :term:`strong references <strong reference>`.
130+
131+
+-----------------------------------+-----------------------------------+
132+
| Borrowed reference API | Strong reference API |
133+
+===================================+===================================+
134+
| :c:func:`PyList_GetItem` | :c:func:`PyList_GetItemRef` |
135+
+-----------------------------------+-----------------------------------+
136+
| :c:func:`PyDict_GetItem` | :c:func:`PyDict_GetItemRef` |
137+
+-----------------------------------+-----------------------------------+
138+
| :c:func:`PyDict_GetItemWithError` | :c:func:`PyDict_GetItemRef` |
139+
+-----------------------------------+-----------------------------------+
140+
| :c:func:`PyDict_GetItemString` | :c:func:`PyDict_GetItemStringRef` |
141+
+-----------------------------------+-----------------------------------+
142+
| :c:func:`PyDict_SetDefault` | :c:func:`PyDict_SetDefaultRef` |
143+
+-----------------------------------+-----------------------------------+
144+
| :c:func:`PyDict_Next` | no direct replacement |
145+
+-----------------------------------+-----------------------------------+
146+
| :c:func:`PyWeakref_GetObject` | :c:func:`PyWeakref_GetRef` |
147+
+-----------------------------------+-----------------------------------+
148+
| :c:func:`PyWeakref_GET_OBJECT` | :c:func:`PyWeakref_GetRef` |
149+
+-----------------------------------+-----------------------------------+
150+
| :c:func:`PyImport_AddModule` | :c:func:`PyImport_AddModuleRef` |
151+
+-----------------------------------+-----------------------------------+
152+
153+
Not all APIs that return borrowed references are problematic. For
154+
example, :c:func:`PyTuple_GetItem` is safe because tuples are immutable.
155+
Similarly, not all uses of the above APIs are problematic. For example,
156+
:c:func:`PyDict_GetItem` is often used for parsing keyword argument
157+
dictionaries in function calls; those keyword argument dictionaries are
158+
effectively private (not accessible by other threads), so using borrowed
159+
references in that context is safe.
160+
161+
Some of these functions were added in Python 3.13. You can use the
162+
`pythoncapi-compat <https://github.com/python/pythoncapi-compat>`_ package
163+
to provide implementations of these functions for older Python versions.
164+
165+
166+
Memory Allocation APIs
167+
======================
168+
169+
Python's memory management C API provides functions in three different
170+
:ref:`allocation domains <allocator-domains>`: "raw", "mem", and "object".
171+
For thread-safety, the free-threaded build requires that only Python objects
172+
are allocated using the object domain, and that all Python object are
173+
allocated using that domain. This differes from the prior Python versions,
174+
where this was only a best practice and not a hard requirement.
175+
176+
.. note::
177+
178+
Search for uses of :c:func:`PyObject_Malloc` in your
179+
extension and check that the allocated memory is used for Python objects.
180+
Use :c:func:`PyMem_Malloc` to allocate buffers instead of
181+
:c:func:`PyObject_Malloc`.
182+
183+
184+
Thread State and GIL APIs
185+
=========================
186+
187+
Python provides a set of functions and macros to manage thread state and the
188+
GIL, such as:
189+
190+
* :c:func:`PyGILState_Ensure` and :c:func:`PyGILState_Release`
191+
* :c:func:`PyEval_SaveThread` and :c:func:`PyEval_RestoreThread`
192+
* :c:macro:`Py_BEGIN_ALLOW_THREADS` and :c:macro:`Py_END_ALLOW_THREADS`
193+
194+
These functions should still be used in the free-threaded build to manage
195+
thread state even when the :term:`GIL` is disabled. For example, if you
196+
create a thread outside of Python, you must call :c:func:`PyGILState_Ensure`
197+
before calling into the Python API to ensure that the thread has a valid
198+
Python thread state.
199+
200+
You should continue to call :c:func:`PyEval_SaveThread` or
201+
:c:macro:`Py_BEGIN_ALLOW_THREADS` around blocking operations, such as I/O or
202+
lock acquisitions, to allow other threads to run the
203+
:term:`cyclic garbage collector <garbage collection>`.
204+
205+
206+
Protecting Internal Extension State
207+
===================================
208+
209+
Your extension may have internal state that was previously protected by the
210+
GIL. You may need to add locking to protect this state. The approach will
211+
depend on your extension, but some common patterns include:
212+
213+
* **Caches**: global caches are a common source of shared state. Consider
214+
using a lock to protect the cache or disabling it in the free-threaded build
215+
if the cache is not critical for performance.
216+
* **Global State**: global state may need to be protected by a lock or moved
217+
to thread local storage. C11 and C++11 provide the ``thread_local`` or
218+
``_Thread_local`` for
219+
`thread-local storage <https://en.cppreference.com/w/c/language/storage_duration>`_.
220+
221+
222+
Building Extensions for the Free-Threaded Build
223+
===============================================
224+
225+
C API extensions need to be built specifically for the free-threaded build.
226+
The wheels, shared libraries, and binaries are indicated by a ``t`` suffix.
227+
228+
* `pypa/manylinux <https://github.com/pypa/manylinux>`_ supports the
229+
free-threaded build, with the ``t`` suffix, such as ``python3.13t``.
230+
* `pypa/cibuildwheel <https://github.com/pypa/cibuildwheel>`_ supports the
231+
free-threaded build if you set
232+
`CIBW_FREE_THREADED_SUPPORT <https://cibuildwheel.pypa.io/en/stable/options/#free-threaded-support>`_.
233+
234+
Limited C API and Stable ABI
235+
............................
236+
237+
The free-threaded build does not currently support the
238+
:ref:`Limited C API <limited-c-api>` or the stable ABI. If you use
239+
`setuptools <https://setuptools.pypa.io/en/latest/setuptools.html>`_ to build
240+
your extension and currently set ``py_limited_api=True`` you can use
241+
``py_limited_api=not sysconfig.get_config_var("Py_GIL_DISABLED")`` to opt out
242+
of the limited API when building with the free-threaded build.
243+
244+
.. note::
245+
You will need to build separate wheels specifically for the free-threaded
246+
build. If you currently use the stable ABI, you can continue to build a
247+
single wheel for multiple non-free-threaded Python versions.
248+
249+
250+
Windows
251+
.......
252+
253+
Due to a limitation of the official Windows installer, you will need to
254+
manually define ``Py_GIL_DISABLED=1`` when building extensions from source.

Doc/howto/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Python Library Reference.
3232
isolating-extensions.rst
3333
timerfd.rst
3434
mro.rst
35+
free-threading-extensions.rst
3536

3637
General:
3738

0 commit comments

Comments
 (0)