Sub interpreters in Python are not well documented or even well supported. The following is to the best of my undestanding. It seems to work well in practice.

Threre are two important concepts to understand when dealing with threads and sub interpreters in Python. First, the Python interpreter is not really multi threaded. It has a Global Interpreter Lock (GIL) that needs to be acquired to perform almost any Python operation (there are a few rare exceptions to this rule).

Second, every combination of thread and sub interpreter has to have its own thread state. The interpreter creates a thread state for every thread managed by it, but if you want to use Python from a thread not created by that interpreter, you need to create a new thread state.

First you need to create the sub interpreters:

Initialize Python

Py_Initialize();

Initialize Python thread support

Required if you plan to call Python from multiple threads). This call also acquires the GIL.

PyEval_InitThreads();

Save the current thread state

I could have used PyEval_SaveThread(), but one of its side effects is releasing the GIL, which then needs to be reacquired.

PyThreadState* _main = PyThreadState_Get();

Create the sub interpreters

PyThreadState* ts1 = Py_NewInterpreter();
PyThreadState* ts2 = Py_NewInterpreter();

Restore the main interpreter thread state

PyThreadState_Swap(_main);

We now have two thread states for the sub interpreters. These thread states are only valid in the thread where they were created. Every thread that wants to use one of the sub interpreters needs to create a thread state for that combination of thread and interpreter.

Using a sub interpreter from a new thread

Here is an example code for using a sub interpreter in a new thread that is not created by the sub interpreter. The new thread must acquire the GIL, create a new thread state for the thread and interpretere combination and make it the current thread state. At the end the reverse must be done to clean up.

void do_stuff_in_thread(PyInterpreterState* interp)
{
    // acquire the GIL
    PyEval_AcquireLock(); 

    // create a new thread state for the the sub interpreter interp
    PyThreadState* ts = PyThreadState_New(interp);

    // make ts the current thread state
    PyThreadState_Swap(ts);

    // at this point:
    // 1. You have the GIL
    // 2. You have the right thread state - a new thread state (this thread was not created by python) in the context of interp

    // PYTHON WORK HERE

    // release ts
    PyThreadState_Swap(NULL);

    // clear and delete ts
    PyThreadState_Clear(ts);
    PyThreadState_Delete(ts);

    // release the GIL
    PyEval_ReleaseLock(); 
}

Using a sub interpreter from a new thread (post Python 3.3)

The previous do_stuff_in_thread() still works with all current Python versions. However, Python 3.3 deprecated PyEval_AcquireLock()/PyEval_ReleaseLock(), which resulted in a bit of a conundrum.

The only documented way to release the GIL is by calling PyEval_ReleaseThread() or PyEval_SaveThread(), both of which require a thread state, while cleaning and deleting the current thread state requires the GIL to be held. That means that one can either release the GIL or clean up the thread state, but not both.

Fortunately, there is a solution - PyThreadState_DeleteCurrent() deletes the current thread state and then releases the GIL. [This API has only been documented since 3.9, but it existed since Python 2.7 at least]

This modified do_stuff_in_thread() also works with all current Python versions.

void do_stuff_in_thread(PyInterpreterState* interp)
{
    // create a new thread state for the the sub interpreter interp
    PyThreadState* ts = PyThreadState_New(interp);

    // make it the current thread state and acquire the GIL
    PyEval_RestoreThread(ts);

    // at this point:
    // 1. You have the GIL
    // 2. You have the right thread state - a new thread state (this thread was not created by python) in the context of interp

    // PYTHON WORK HERE

    // clear ts
    PyThreadState_Clear(ts);

    // delete the current thread state and release the GIL
    PyThreadState_DeleteCurrent();
}

Now each thread can do the following:

Thread1

do_stuff_in_thread(ts1->interp);

Thread2

do_stuff_in_thread(ts1->interp);

Thread3

do_stuff_in_thread(ts2->interp);

Calling Py_Finalize() destroys all sub interpreters. Alternatively they can be destroyed manually. This needs to be done in the main thread, using the thread states created when creating the sub interpreters. At the end make the main interpreter thread state the current state.

// make ts1 the current thread state
PyThreadState_Swap(ts1);
// destroy the interpreter
Py_EndInterpreter(ts1);

// make ts2 the current thread state
PyThreadState_Swap(ts2);
// destroy the interpreter
Py_EndInterpreter(ts2);

// restore the main interpreter thread state
PyThreadState_Swap(_main);

I hope this make things a bit clearer.

I have a small complete example written in C++ on github, and another also on github (post Python 3.3 variant).

Answer from sterin on Stack Overflow
Top answer
1 of 3
44

Sub interpreters in Python are not well documented or even well supported. The following is to the best of my undestanding. It seems to work well in practice.

Threre are two important concepts to understand when dealing with threads and sub interpreters in Python. First, the Python interpreter is not really multi threaded. It has a Global Interpreter Lock (GIL) that needs to be acquired to perform almost any Python operation (there are a few rare exceptions to this rule).

Second, every combination of thread and sub interpreter has to have its own thread state. The interpreter creates a thread state for every thread managed by it, but if you want to use Python from a thread not created by that interpreter, you need to create a new thread state.

First you need to create the sub interpreters:

Initialize Python

Py_Initialize();

Initialize Python thread support

Required if you plan to call Python from multiple threads). This call also acquires the GIL.

PyEval_InitThreads();

Save the current thread state

I could have used PyEval_SaveThread(), but one of its side effects is releasing the GIL, which then needs to be reacquired.

PyThreadState* _main = PyThreadState_Get();

Create the sub interpreters

PyThreadState* ts1 = Py_NewInterpreter();
PyThreadState* ts2 = Py_NewInterpreter();

Restore the main interpreter thread state

PyThreadState_Swap(_main);

We now have two thread states for the sub interpreters. These thread states are only valid in the thread where they were created. Every thread that wants to use one of the sub interpreters needs to create a thread state for that combination of thread and interpreter.

Using a sub interpreter from a new thread

Here is an example code for using a sub interpreter in a new thread that is not created by the sub interpreter. The new thread must acquire the GIL, create a new thread state for the thread and interpretere combination and make it the current thread state. At the end the reverse must be done to clean up.

void do_stuff_in_thread(PyInterpreterState* interp)
{
    // acquire the GIL
    PyEval_AcquireLock(); 

    // create a new thread state for the the sub interpreter interp
    PyThreadState* ts = PyThreadState_New(interp);

    // make ts the current thread state
    PyThreadState_Swap(ts);

    // at this point:
    // 1. You have the GIL
    // 2. You have the right thread state - a new thread state (this thread was not created by python) in the context of interp

    // PYTHON WORK HERE

    // release ts
    PyThreadState_Swap(NULL);

    // clear and delete ts
    PyThreadState_Clear(ts);
    PyThreadState_Delete(ts);

    // release the GIL
    PyEval_ReleaseLock(); 
}

Using a sub interpreter from a new thread (post Python 3.3)

The previous do_stuff_in_thread() still works with all current Python versions. However, Python 3.3 deprecated PyEval_AcquireLock()/PyEval_ReleaseLock(), which resulted in a bit of a conundrum.

The only documented way to release the GIL is by calling PyEval_ReleaseThread() or PyEval_SaveThread(), both of which require a thread state, while cleaning and deleting the current thread state requires the GIL to be held. That means that one can either release the GIL or clean up the thread state, but not both.

Fortunately, there is a solution - PyThreadState_DeleteCurrent() deletes the current thread state and then releases the GIL. [This API has only been documented since 3.9, but it existed since Python 2.7 at least]

This modified do_stuff_in_thread() also works with all current Python versions.

void do_stuff_in_thread(PyInterpreterState* interp)
{
    // create a new thread state for the the sub interpreter interp
    PyThreadState* ts = PyThreadState_New(interp);

    // make it the current thread state and acquire the GIL
    PyEval_RestoreThread(ts);

    // at this point:
    // 1. You have the GIL
    // 2. You have the right thread state - a new thread state (this thread was not created by python) in the context of interp

    // PYTHON WORK HERE

    // clear ts
    PyThreadState_Clear(ts);

    // delete the current thread state and release the GIL
    PyThreadState_DeleteCurrent();
}

Now each thread can do the following:

Thread1

do_stuff_in_thread(ts1->interp);

Thread2

do_stuff_in_thread(ts1->interp);

Thread3

do_stuff_in_thread(ts2->interp);

Calling Py_Finalize() destroys all sub interpreters. Alternatively they can be destroyed manually. This needs to be done in the main thread, using the thread states created when creating the sub interpreters. At the end make the main interpreter thread state the current state.

// make ts1 the current thread state
PyThreadState_Swap(ts1);
// destroy the interpreter
Py_EndInterpreter(ts1);

// make ts2 the current thread state
PyThreadState_Swap(ts2);
// destroy the interpreter
Py_EndInterpreter(ts2);

// restore the main interpreter thread state
PyThreadState_Swap(_main);

I hope this make things a bit clearer.

I have a small complete example written in C++ on github, and another also on github (post Python 3.3 variant).

2 of 3
1

just need to point out a problem in @sterin's answer, in part Using a sub interpreter from a new thread (post Python 3.3)

  • PyThreadState_New must be called when GIL is held
  • PyEval_RestoreThread will acquire the GIL, so it must not be called with GIL held, otherwise a deadlock will show up.

As a result, you need to use PyThreadState_Swap in this case instead of PyEval_RestoreThread

Also, you can verify which interpreter is being used by

int64_t interp_id = PyInterpreterState_GetID(interp);
🌐
GitHub
github.com › mloskot › workshop › blob › master › python › serial_subinterpreters.cpp
workshop/python/serial_subinterpreters.cpp at master · mloskot/workshop
PyThreadState* interp_sub1 = Py_NewInterpreter(); // this thread state is made in current (implicit swap)
Author   mloskot
🌐
Python
docs.python.org › 3 › c-api › init.html
Initialization, Finalization, and Threads — Python 3.13.5 documentation
PyInterpreterConfig config = { .use_main_obmalloc = 0, .allow_fork = 0, .allow_exec = 0, .allow_threads = 1, .allow_daemon_threads = 0, .check_multi_interp_extensions = 1, .gil = PyInterpreterConfig_OWN_GIL, }; PyThreadState *tstate = NULL; PyStatus status = Py_NewInterpreterFromConfig(&tstate, &config); if (PyStatus_Exception(status)) { Py_ExitStatusException(status); }
🌐
Vstinner
vstinner.github.io › cpython-pass-tstate.html
Pass the Python thread state explicitly — Victor Stinner blog 3
January 8, 2020 - For example, I modified Python so Py_NewInterpreter() and Py_EndInterpreter() (create and finalize a subinterpreter) share more code with Py_Initialize() and Py_Finalize() (create and finalize the main interpreter): new_interpreter() should reuse more Py_InitializeFromConfig() code.
🌐
Massachusetts Institute of Technology
mit.edu › people › amliu › vrut › python › api › initialization.html
8. Initialization, Finalization, and Threads
Undo all initializations made by Py_Initialize() and subsequent use of Python/C API functions, and destroy all sub-interpreters (see Py_NewInterpreter() below) that were created and not yet destroyed since the last call to Py_Initialize(). Ideally, this frees all memory allocated by the Python ...
🌐
Awasu
awasu.com › weblog › embedding-python › threads
Using Python in a multi-threaded program
November 30, 2014 - If your C/C++ program is multi-threaded, and you want to use the embedded Python interpreter in multiple threads, it is possible, albeit a bit clunky. While it is possible to create multiple interpreters, only one of them can be running at any given time, and switching between them requires ...
🌐
docs.python.org
docs.python.org › 3 › c-api › init.html
Initialization, finalization, and threads — Python 3.14.3 documentation
This page has been split up into the following: Interpreter initialization and finalization, Thread states and the global interpreter lock, Synchronization primitives, Thread-local storage support,...
Find elsewhere
🌐
Izzys
izzys.casa › 2024 › 08 › 463-python-interpreters
500 Python Interpreters • Izzy Muerte
August 19, 2024 - This structure allows users to set a few “hard” settings. For example, you can disable the threading module, os.execv(), and process forking (though subprocess does not create ...
🌐
Stack Overflow
stackoverflow.com › questions › 23675729 › import-module-deadlock-using-py-newinterpreter
python - Import module deadlock (using Py_NewInterpreter) - Stack Overflow
Py_Initialize(); PyThreadState* pGlobalThreadState = PyThreadState_Get(); PyThreadState* pInterpreterThreadState = Py_NewInterpreter(); PyThreadState_Swap(pInterpreterThreadState); PyRun_SimpleString("import PySide"); // Importing PySide deadlocks ...
🌐
ThinhDA
thinhdanggroup.github.io › subinterpreter
Python 3.12 Subinterpreters: A New Era of Concurrency - ThinhDA
September 23, 2023 - To create a new subinterpreter, ... as Py_NewInterpreterFromConfig(). This function can be used to create an interpreter with its own Global Interpreter Lock (GIL), which allows Python programs to take full advantage of multiple CPU cores. In the proposed PEP 554, the create() method from the interpreters module is used to create a new subinterpreter. This method returns an Interpreter object that represents a new Python interpreter. For example, you can create ...
🌐
GitHub
github.com › pytorch › pytorch › issues › 20695
Py_NewInterpreter() · Issue #20695 · pytorch/pytorch
May 20, 2019 - You must use PyEval_SaveThread() / PyEval_RestoreThread() to support Py_NewInterpreter().
Published   May 20, 2019
Author   Roffild
🌐
Simon Fraser University
www2.cs.sfu.ca › CourseCentral › 120 › havens › python-2.7-docs-html › c-api › init.html
Initialization, Finalization, and Threads — Python v2.7 documentation
September 11, 2010 - Undo all initializations made by Py_Initialize() and subsequent use of Python/C API functions, and destroy all sub-interpreters (see Py_NewInterpreter() below) that were created and not yet destroyed since the last call to Py_Initialize(). Ideally, this frees all memory allocated by the Python ...
🌐
Python.org
discuss.python.org › python help
Embedded python problem: Py_NewInterpreter crashed after previous interpreter was removed using Py_EndInterpreter - Python Help - Discussions on Python.org
April 30, 2020 - I`m trying to control python execution from c++ using embedded python: My target is to start, execute in individual environments and stop multiple python executions individually on purpose.
🌐
GitHub
github.com › python › cpython › issues › 98608
Expose _Py_NewInterpreter() as Py_NewInterpreterFromConfig() · Issue #98608 · python/cpython
A while back I added _Py_NewInterpreter() (a "private" API) to support configuring the new interpreter. Ultimately, I'd like to adjust the signature a little and then make the function part of the public API (as Py_NewInterpreterFromConf...
🌐
The Coding Forums
thecodingforums.com › archive › archive › python
Py_NewInterpreter(), is this a bug in the python core? | Python | Coding Forums
July 13, 2006 - I only used Py_NewInterpreter to have a fresh 'import sys'. Somebody enlighten me, please. Martin · Click to expand... If you try to replicate the code from the linux journal (http://www.linuxjournal.com/article/3641) and compile with Py_DEBUG defined, when running it you will find that you get an exception and a fatal error message from the python core.
🌐
Post.Byes
post.bytes.com › home › forum › topic › python
Difference between Py_NewInterpreter() and Py_Initialize() - Post.Byes
I am working on a C++ win32 DLL which has calls to python, mainly import some modules, call some functions and return some values. Right now I am using the Py_Initialize() and Py_Uninitialize() functions to get to the embedded interpreter. I am having an issue with making repeated calls to the function(s) that my DLL exports,