Skip to content

Commit 1107c09

Browse files
b-passpre-commit-ci[bot]Copilothenryiii
authored
docs: Add documentation for mod_gil_not_used and multiple_interpreters (#5659)
* Add documentation (advanced/misc) for mod_gil_not_used and multiple_interpreters options * Add some comparison of free-threading vs sub-interpreters * Formatting * Reword for clarity * style: pre-commit fixes * Suggested changes, including a long-winded explaination of parallelism in python today * Slight formatting updates and GPT suggested clarifications * Better section title * Update docs/advanced/misc.rst Copilot suggestion Co-authored-by: Copilot <[email protected]> * Update docs/advanced/misc.rst Copilot suggestion Co-authored-by: Copilot <[email protected]> * Update docs/advanced/misc.rst * Add a deadlock warning, and minor corrections * Rewrite the example to avoid free-threading risky behaviors * Oops, missed a line in the example --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Copilot <[email protected]> Co-authored-by: Henry Schreiner <[email protected]>
1 parent af231a6 commit 1107c09

File tree

1 file changed

+158
-0
lines changed

1 file changed

+158
-0
lines changed

docs/advanced/misc.rst

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ The ``call_go`` wrapper can also be simplified using the ``call_guard`` policy
122122
m.def("call_go", &call_go, py::call_guard<py::gil_scoped_release>());
123123
124124
125+
.. _commongilproblems:
126+
125127
Common Sources Of Global Interpreter Lock Errors
126128
==================================================================
127129

@@ -153,6 +155,162 @@ following checklist.
153155
within pybind11 that will throw exceptions on certain GIL handling errors
154156
(reference counting operations).
155157

158+
Free-threading support
159+
==================================================================
160+
161+
pybind11 supports the experimental free-threaded builds of Python versions 3.13+.
162+
pybind11's internal data structures are thread safe. To enable your modules to be used with
163+
free-threading, pass the :class:`mod_gil_not_used` tag as the third argument to
164+
``PYBIND11_MODULE``.
165+
166+
For example:
167+
168+
.. code-block:: cpp
169+
:emphasize-lines: 1
170+
171+
PYBIND11_MODULE(example, m, py::mod_gil_not_used()) {
172+
py::class_<Animal> animal(m, "Animal");
173+
// etc
174+
}
175+
176+
Importantly, enabling your module to be used with free-threading is also your promise that
177+
your code is thread safe. Modules must still be built against the Python free-threading branch to
178+
enable free-threading, even if they specify this tag. Adding this tag does not break
179+
compatibility with non-free-threaded Python.
180+
181+
Sub-interpreter support
182+
==================================================================
183+
184+
pybind11 supports isolated sub-interpreters, which are stable in Python 3.12+. pybind11's
185+
internal data structures are sub-interpreter safe. To enable your modules to be imported in
186+
isolated sub-interpreters, pass the :func:`multiple_interpreters::per_interpreter_gil()`
187+
tag as the third or later argument to ``PYBIND11_MODULE``.
188+
189+
For example:
190+
191+
.. code-block:: cpp
192+
:emphasize-lines: 1
193+
194+
PYBIND11_MODULE(example, m, py::multiple_interpreters::per_interpreter_gil()) {
195+
py::class_<Animal> animal(m, "Animal");
196+
// etc
197+
}
198+
199+
Best Practices for Sub-interpreter Safety:
200+
201+
- Your initialization function will run for each interpreter that imports your module.
202+
203+
- Never share Python objects across different sub-interpreters.
204+
205+
- Avoid global/static state whenever possible. Instead, keep state within each interpreter,
206+
such as in instance members tied to Python objects, :func:`globals()`, and the interpreter
207+
state dict.
208+
209+
- Modules without any global/static state in their C++ code may already be sub-interpreter safe
210+
without any additional work!
211+
212+
- Avoid trying to "cache" Python objects in C++ variables across function calls (this is an easy
213+
way to accidentally introduce sub-interpreter bugs).
214+
215+
- While sub-interpreters each have their own GIL, there can now be multiple independent GILs in one
216+
program, so concurrent calls into a module from two different sub-interpreters are still
217+
possible. Therefore, your module still needs to consider thread safety.
218+
219+
pybind11 also supports "legacy" sub-interpreters which shared a single global GIL. You can enable
220+
legacy-only behavior by using the :func:`multiple_interpreters::shared_gil()` tag in
221+
``PYBIND11_MODULE``.
222+
223+
You can explicitly disable sub-interpreter support in your module by using the
224+
:func:`multiple_interpreters::not_supported()` tag. This is the default behavior if you do not
225+
specify a multiple_interpreters tag.
226+
227+
Concurrency and Parallelism in Python with pybind11
228+
===================================================
229+
230+
Sub-interpreter support does not imply free-threading support or vice versa. Free-threading safe
231+
modules can still have global/static state (as long as access to them is thread-safe), but
232+
sub-interpreter safe modules cannot. Likewise, sub-interpreter safe modules can still rely on the
233+
GIL, but free-threading safe modules cannot.
234+
235+
Here is a simple example module which has a function that calculates a value and returns the result
236+
of the previous calculation.
237+
238+
.. code-block:: cpp
239+
240+
PYBIND11_MODULE(example, m) {
241+
static size_t seed = 0;
242+
m.def("calc_next", []() {
243+
auto old = seed;
244+
seed = (seed + 1) * 10;
245+
return old;
246+
});
247+
248+
This module is not free-threading safe because there is no synchronization on the number variable.
249+
It is relatively easy to make this free-threading safe. One way is by using atomics, like this:
250+
251+
.. code-block:: cpp
252+
:emphasize-lines: 1,2
253+
254+
PYBIND11_MODULE(example, m, py::mod_gil_not_used()) {
255+
static std::atomic<size_t> seed(0);
256+
m.def("calc_next", []() {
257+
size_t old, next;
258+
do {
259+
old = seed.load();
260+
next = (old + 1) * 10;
261+
} while (!seed.compare_exchange_weak(old, next));
262+
return old;
263+
});
264+
}
265+
266+
The atomic variable and the compare-exchange guarantee a consistent behavior from this function even
267+
when called currently from multiple threads at the same time.
268+
269+
However, the global/static integer is not sub-interpreter safe, because the calls in one
270+
sub-interpreter will change what is seen in another. To fix it, the state needs to be specific to
271+
each interpreter. One way to do that is by storing the state on another Python object, such as a
272+
member of a class. For this simple example, we will store it in :func:`globals`.
273+
274+
.. code-block:: cpp
275+
:emphasize-lines: 1,6
276+
277+
PYBIND11_MODULE(example, m, py::multiple_interpreters::per_interpreter_gil()) {
278+
m.def("calc_next", []() {
279+
if (!py::globals().contains("myseed"))
280+
py::globals()["myseed"] = 0;
281+
size_t old = py::globals()["myseed"];
282+
py::globals()["myseed"] = (old + 1) * 10;
283+
return old;
284+
});
285+
}
286+
287+
This module is sub-interpreter safe, for both ``shared_gil`` ("legacy") and
288+
``per_interpreter_gil`` ("default") varieties. Multiple sub-interpreters could each call this same
289+
function concurrently from different threads. This is safe because each sub-interpreter's GIL
290+
protects it's own Python objects from concurrent access.
291+
292+
However, the module is no longer free-threading safe, for the same reason as before, because the
293+
calculation is not synchronized. We can synchronize it using a Python critical section.
294+
295+
.. code-block:: cpp
296+
:emphasize-lines: 1,5,10
297+
298+
PYBIND11_MODULE(example, m, py::multiple_interpreters::per_interpreter_gil(), py::mod_gil_not_used()) {
299+
m.def("calc_next", []() {
300+
size_t old;
301+
py::dict g = py::globals();
302+
Py_BEGIN_CRITICAL_SECTION(g);
303+
if (!g.contains("myseed"))
304+
g["myseed"] = 0;
305+
old = g["myseed"];
306+
g["myseed"] = (old + 1) * 10;
307+
Py_END_CRITICAL_SECTION();
308+
return old;
309+
});
310+
}
311+
312+
The module is now both sub-interpreter safe and free-threading safe.
313+
156314
Binding sequence data types, iterators, the slicing protocol, etc.
157315
==================================================================
158316

0 commit comments

Comments
 (0)