Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions Doc/c-api/threads.rst
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,61 @@ a thread state that was previously attached for the current thread.
.. seealso::
:pep:`788`

.. _c-api-reuse-thread-state:

Reusing a thread state across repeated calls
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Creating and destroying a :c:type:`PyThreadState` is not free, and is more
expensive on a :term:`free-threaded build`. A foreign thread that calls into
the interpreter many times -- for example, a worker thread in a native thread
pool -- should avoid creating a fresh thread state on every entry and
destroying it on every exit. Instead, set up one thread state when the thread
starts (or lazily on its first call into Python), attach and detach it around
each call, and tear it down once when the thread exits.

Manage the thread state explicitly with :c:func:`PyThreadState_New`, attaching
and detaching it with :c:func:`PyEval_RestoreThread` and
:c:func:`PyEval_SaveThread`. This happens in three distinct phases, at
different points in the thread's life.

When the thread starts, create one thread state for it. ``interp`` is the
target interpreter, captured by the code that created this thread while it held
an attached thread state (for example via :c:func:`PyInterpreterState_Get`)::

PyThreadState *tstate = PyThreadState_New(interp);

Then, on each call into Python -- which may happen many times over the thread's
life -- attach the thread state, make the Python C API calls that require it,
and detach again so the thread does not hold the GIL while off doing non-Python
work::

PyEval_RestoreThread(tstate);
result = CallSomeFunction(); /* your Python C API calls go here */
PyEval_SaveThread();

When the thread is finished calling into Python, destroy the thread state once::

PyEval_RestoreThread(tstate);
PyThreadState_Clear(tstate);
PyThreadState_DeleteCurrent();

The general-purpose entry points for calling in from a foreign thread --
:c:func:`PyThreadState_Ensure` and the older :c:func:`PyGILState_Ensure` -- do
*not* guarantee a persistent thread state: their thread-state lifetime is
deliberately implementation-defined, so a matched acquire/release pair may
create and destroy a thread state each time. Use :c:func:`PyThreadState_New`,
as shown here, whenever you specifically want to reuse one thread state across
calls.

The code that created the foreign thread must arrange for the shutdown sequence
to run before the thread exits, and before :c:func:`Py_FinalizeEx` is called.
If interpreter finalization begins first, the shutdown
:c:func:`PyEval_RestoreThread` call will hang the thread rather than return (see
:ref:`cautions-regarding-runtime-finalization`). If the thread exits without
running the shutdown sequence, the thread state is leaked for the remainder of
the process.

.. _c-api-attach-detach:

Attaching/detaching thread states
Expand Down
Loading