我正在将 Python 嵌入到 C++ 应用程序中,我认为我对此感到有些困惑,PyGILState_Ensure/PyGILState_Release这最终会导致在线程Py_FinalizeEx中挂起threading._shutdown()(由调用Py_FinalizeEx) 。join()
PyGILState_Ensure/PyGILState_Release
Py_FinalizeEx
threading._shutdown()
join()
在初始化期间我正在调用:
Py_InitializeEx(0); // Skip installing signal handlers auto gil = PyGILState_Ensure(); // ... running Python code PyGILState_Release(gil);
每当一个线程使用 Python(可以是多个 C 线程)时,我pyscope在函数的开头使用:
pyscope
#define pyscope() \ PyGILState_STATE gstate = PyGILState_Ensure(); \ utils::scope_guard sggstate([&]() \ { \ PyGILState_Release(gstate); \ });
当我想释放 Python 时,我会调用(从 C 线程,不一定是初始化 Python 的线程):
PyGILState_STATE gstate = PyGILState_Ensure(); int res = Py_FinalizeEx(); // <--- HANGS!!!
调试和阅读代码发现它在线程连接期间挂起。我可以通过运行以下代码PyRun_SimpleString(在 Py_FinalizeEx 之前运行)来重现死锁:
PyRun_SimpleString
import threading for t in threading.enumerate(): print('get_ident: {} ; native: {}'.format(t.ident, t.native_id)) if not threading.current_thread().ident == t.ident: t.join()
最后,我没有使用 PyEval_SaveThread/RestoreThread,也许我不得不使用,但我不明白如何将它们与 GIL_Ensure/Release 一起使用,因为我看到它们内部也在获取和删除 GIL。
你知道为什么会发生死锁以及如何解决这个问题吗?
这是因为每次获取 gil 时都必须释放它,主线程首先获取一次 GIL,因此必须通过在主线程中调用PyEval_SaveThread来重置主线程中的初始 GIL 计数。
Py_InitializeEx(0); // Skip installing signal handlers PyThreadState* tstate = PyEval_SaveThread(); auto gil = PyGILState_Ensure(); // ... running Python code PyGILState_Release(gil);
最小可重复示例
#include <iostream> #include <Python.h> #include <string> #include <windows.h> #include <processenv.h> #include <thread> namespace util { template<class F> class scope_guard { F func_; bool active_; public: scope_guard(F func) : func_(std::move(func)), active_(true) { } ~scope_guard() { if (active_) func_(); } }; } // namespace util #define pyscope() \ PyGILState_STATE gstate = PyGILState_Ensure(); \ util::scope_guard sggstate([&]() \ { \ PyGILState_Release(gstate); \ }) void worker() { PyGILState_STATE gstate = PyGILState_Ensure(); PyRun_SimpleString("from time import time,ctime\n" "print('Today is', ctime(time()))\n"); if (Py_FinalizeEx() < 0) { exit(120); } } void worker2() { pyscope(); PyRun_SimpleString("from time import time,ctime\n" "print('Today is', ctime(time()))\n"); } int main(int argc, char *argv[]) { SetEnvironmentVariableA("PYTHONHOME", "C:\\Users\\blabla\\.conda\\envs\\py310"); wchar_t *program = Py_DecodeLocale(argv[0], NULL); if (program == NULL) { fprintf(stderr, "Fatal error: cannot decode argv[0]\n"); exit(1); } Py_SetProgramName(program); Py_Initialize(); PyThreadState* tstate = PyEval_SaveThread(); auto thread2 = std::thread(worker2); thread2.join(); auto GIL = PyGILState_Ensure(); auto thread1 = std::thread(worker); PyGILState_Release(GIL); thread1.join(); PyMem_RawFree(program); return 0; }