https://github.com/python/cpython/commit/5717518fb3ed6f34716683e08ec372267d871d21
commit: 5717518fb3ed6f34716683e08ec372267d871d21
branch: main
author: Gregory P. Smith <[email protected]>
committer: gpshead <[email protected]>
date: 2026-06-29T17:05:02-07:00
summary:

gh-146219: Document reusing a thread state across repeated foreign-thread calls 
(GH-146221)

* Document reusing a thread state across repeated foreign-thread calls

Add a subsection under "Non-Python created threads" explaining the
performance cost of creating/destroying a PyThreadState on every
Ensure/Release cycle and showing how to keep one alive for the
thread's lifetime instead.

* add a comma

Co-authored-by: Peter Bierma <[email protected]>

---------

Co-authored-by: Peter Bierma <[email protected]>

files:
M Doc/c-api/threads.rst

diff --git a/Doc/c-api/threads.rst b/Doc/c-api/threads.rst
index ca34abd73d8423..67a9ac54909727 100644
--- a/Doc/c-api/threads.rst
+++ b/Doc/c-api/threads.rst
@@ -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

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to