@@ -242,6 +242,61 @@ a thread state that was previously attached for the current thread.
242242.. seealso ::
243243 :pep: `788 `
244244
245+ .. _c-api-reuse-thread-state :
246+
247+ Reusing a thread state across repeated calls
248+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
249+
250+ Creating and destroying a :c:type: `PyThreadState ` is not free, and is more
251+ expensive on a :term: `free-threaded build `. A foreign thread that calls into
252+ the interpreter many times -- for example, a worker thread in a native thread
253+ pool -- should avoid creating a fresh thread state on every entry and
254+ destroying it on every exit. Instead, set up one thread state when the thread
255+ starts (or lazily on its first call into Python), attach and detach it around
256+ each call, and tear it down once when the thread exits.
257+
258+ Manage the thread state explicitly with :c:func: `PyThreadState_New `, attaching
259+ and detaching it with :c:func: `PyEval_RestoreThread ` and
260+ :c:func: `PyEval_SaveThread `. This happens in three distinct phases, at
261+ different points in the thread's life.
262+
263+ When the thread starts, create one thread state for it. ``interp `` is the
264+ target interpreter, captured by the code that created this thread while it held
265+ an attached thread state (for example via :c:func: `PyInterpreterState_Get `)::
266+
267+ PyThreadState *tstate = PyThreadState_New(interp);
268+
269+ Then, on each call into Python -- which may happen many times over the thread's
270+ life -- attach the thread state, make the Python C API calls that require it,
271+ and detach again so the thread does not hold the GIL while off doing non-Python
272+ work::
273+
274+ PyEval_RestoreThread(tstate);
275+ result = CallSomeFunction(); /* your Python C API calls go here */
276+ PyEval_SaveThread();
277+
278+ When the thread is finished calling into Python, destroy the thread state once::
279+
280+ PyEval_RestoreThread(tstate);
281+ PyThreadState_Clear(tstate);
282+ PyThreadState_DeleteCurrent();
283+
284+ The general-purpose entry points for calling in from a foreign thread --
285+ :c:func: `PyThreadState_Ensure ` and the older :c:func: `PyGILState_Ensure ` -- do
286+ *not * guarantee a persistent thread state: their thread-state lifetime is
287+ deliberately implementation-defined, so a matched acquire/release pair may
288+ create and destroy a thread state each time. Use :c:func: `PyThreadState_New `,
289+ as shown here, whenever you specifically want to reuse one thread state across
290+ calls.
291+
292+ The code that created the foreign thread must arrange for the shutdown sequence
293+ to run before the thread exits, and before :c:func: `Py_FinalizeEx ` is called.
294+ If interpreter finalization begins first, the shutdown
295+ :c:func: `PyEval_RestoreThread ` call will hang the thread rather than return (see
296+ :ref: `cautions-regarding-runtime-finalization `). If the thread exits without
297+ running the shutdown sequence, the thread state is leaked for the remainder of
298+ the process.
299+
245300.. _c-api-attach-detach :
246301
247302Attaching/detaching thread states
0 commit comments