Вызов Py_EndInterpreter из рабочего потока C

Устаревание PyEval_ReleaseLock в Python привело к проблеме в нашей кодовой базе: мы хотим завершить интерпретатор Python из функции обратного вызова C, используя Py_EndInterpreter

Поэтому для этого в Python Docs сказано, что вы должны удерживайте GIL при вызове этой функции:

void Py_EndInterpreter(PyThreadState *tstate)

Уничтожить (суб-)интерпретатор, представленный данным состоянием потока. Заданное состояние потока должно быть текущим состоянием потока. См. обсуждение состояний потока ниже. Когда вызов возвращается, текущее состояние потока равно NULL. Все состояния потока, связанные с этим интерпретатором, уничтожаются. (Глобальная блокировка интерпретатора должна быть удержана перед вызовом этой функции и все еще удерживается, когда она возвращается.) Py_FinalizeEx() уничтожит все подинтерпретаторы, которые не были явно заблокированы. уничтожено в тот момент.

Здорово! Итак, мы вызываем PyEval_RestoreThread, чтобы восстановить состояние нашего потока до потока, который мы собираемся завершить, а затем вызываем Py_EndInterpreter.

// Acquire the GIL
PyEval_RestoreThread(thread);

// Tear down the interpreter.
Py_EndInterpreter(thread);

// Now what? We still hold the GIL and we no longer have a valid thread state.
// Previously we did PyEval_ReleaseLock here, but that is now deprecated.

В документации для PyEval_ReleaseLock сказано, что мы должны использовать либо PyEval_SaveThread, либо PyEval_ReleaseThread. В документации PyEval_ReleaseThread говорится, что состояние входного потока не должно быть NULL. Хорошо, но мы не можем передать состояние недавно удаленного потока.

PyEval_SaveThread столкнется с утверждением отладки, если вы попытаетесь вызвать его после вызова Py_EndInterpreter, так что это тоже не вариант.

Итак, мы в настоящее время реализовали хак, чтобы обойти эту проблему — мы сохраняем состояние потока, вызывающего Py_InitializeEx, в глобальной переменной и переключаемся на него после вызова Py_EndInterpreter.

// Acquire the GIL
PyEval_RestoreThread(thread);

// Tear down the interpreter.
Py_EndInterpreter(thread);

// Swap to the main thread state.
PyThreadState_Swap(g_init.thread_state_);
PyEval_SaveThread(); // Release the GIL. Probably.

Какое правильное решение здесь? Кажется, что встроенный Python — это запоздалая мысль для этого API.

Аналогичный вопрос: PyEval_InitThreads в Python 3: как/когда его вызывать? (сага продолжается до отвращения)


person Justin Moore    schedule 18.09.2019    source источник