Ответ 1
Подинтерпретаторы в Python плохо документированы или даже не поддерживаются. Следующее в меру моего понимания. Кажется, это хорошо работает на практике.
Есть два важных понятия, которые нужно понимать при работе с потоками и субинтерпретаторами в Python. Во-первых, интерпретатор Python не является многопоточным. Он имеет глобальную блокировку интерпретатора (GIL), которую необходимо получить для выполнения практически любой операции с Python (из этого правила есть несколько редких исключений).
Во-вторых, каждая комбинация потока и подчиненного интерпретатора должна иметь свое собственное состояние потока. Интерпретатор создает состояние потока для каждого потока, которым он управляет, но если вы хотите использовать Python из потока, не созданного этим интерпретатором, вам необходимо создать новое состояние потока.
Сначала вам нужно создать подчиненные интерпретаторы:
Инициализировать Python
Py_Initialize();
Инициализация поддержки потока Python
Требуется, если вы планируете вызывать Python из нескольких потоков). Этот звонок также приобретает GIL.
PyEval_InitThreads();
Сохранить текущее состояние потока
Я мог бы использовать PyEval_SaveThread()
, но одним из его побочных эффектов является освобождение GIL, который затем необходимо повторно получить.
PyThreadState* _main = PyThreadState_Get();
Создайте подчиненные переводчики
PyThreadState* ts1 = Py_NewInterpreter();
PyThreadState* ts2 = Py_NewInterpreter();
Восстановление основного состояния потока интерпретатора
PyThreadState_Swap(_main);
Теперь у нас есть два состояния потока для подчиненных интерпретаторов. Эти состояния потока действительны только в том потоке, в котором они были созданы. Каждый поток, который хочет использовать один из подчиненных интерпретаторов, должен создать состояние потока для этой комбинации потока и интерпретатора.
Использование подчиненного интерпретатора из нового потока
Вот пример кода для использования подчиненного интерпретатора в новом потоке, который не создан подчиненным интерпретатором. Новый поток должен получить GIL, создать новое состояние потока для комбинации потока и interpretere и сделать его текущим состоянием потока. В конце должно быть сделано обратное, чтобы очистить.
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();
}
Использование подчиненного интерпретатора из новой темы (пост Python 3.3)
Предыдущий do_stuff_in_thread()
прежнему работает со всеми текущими версиями Python. Тем не менее, Python 3.3 устарел PyEval_AcquireLock()
/PyEval_ReleaseLock()
, что привело к некоторой загадке.
Единственный документированный способ высвобождения GIL - это вызов PyEval_ReleaseThread()
или PyEval_SaveThread()
, оба из которых требуют состояния потока, в то время как очистка и удаление текущего состояния потока требует сохранения GIL. Это означает, что можно либо освободить GIL, либо очистить состояние потока, но не оба.
К счастью, есть решение - PyThreadState_DeleteCurrent()
удаляет текущее состояние потока и затем освобождает GIL. К сожалению, эта функция не задокументирована, поэтому я не уверен на 100%, что это публичный API.
Этот модифицированный do_stuff_in_thread()
также работает со всеми текущими версиями Python.
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();
}
Теперь каждый поток может делать следующее:
Резьба1
do_stuff_in_thread(ts1->interp);
Резьба2
do_stuff_in_thread(ts1->interp);
Thread3
do_stuff_in_thread(ts2->interp);
Вызов Py_Finalize()
уничтожает все подчиненные интерпретаторы. В качестве альтернативы они могут быть уничтожены вручную. Это должно быть сделано в основном потоке, используя состояния потока, созданные при создании подчиненных интерпретаторов. В конце сделайте основной поток интерпретатора текущим состоянием.
// 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);
Надеюсь, это прояснит ситуацию.
У меня есть небольшой полный пример, написанный на C++ на GitHub, а другие также на GitHub (пост Python 3.3 варианта).