多线程C++环境嵌入Python时TaskA执行挂死问题求助(Windows 10+MSVC+Python3.13.5)
多线程C++环境嵌入Python时TaskA执行挂死问题求助(Windows 10+MSVC+Python3.13.5)
看起来你遇到了在Taskflow多线程任务里操作Python GIL导致的挂死问题,我来帮你拆解下原因和解决办法:
问题环境
- 系统:Windows 10
- 编译工具:MSVC
- Python版本:3.13.5
- 依赖库:Taskflow(仅头文件)
核心挂死原因
你的代码里有一个容易被忽略的关键细节:调用PyEval_InitThreads()后,主线程会自动持有GIL,但你没有主动释放它。
Taskflow的工作线程在执行TaskA时,PyGILState_Ensure()会尝试获取GIL,可此时主线程一直攥着GIL不放,导致工作线程无限等待,最终整个程序彻底挂死——连Ctrl+C都无法中断,因为主线程阻塞在Taskflow的等待逻辑上,工作线程又卡在GIL获取的阻塞中,形成了死锁。
解决方案
只需要在PyEval_InitThreads()之后,立即释放主线程持有的GIL,让工作线程能够正常获取GIL即可。
关键修改点
在Python初始化代码段中添加释放GIL的逻辑:
Py_Initialize(); PyEval_InitThreads(); // 新增:释放主线程持有的GIL(PyEval_InitThreads会自动获取GIL,必须主动释放) PyEval_ReleaseThread(PyThreadState_Get()); if (!Py_IsInitialized()) { std::cerr << "Python failed to initialize\n"; return 1; }
也可以用Python官方提供的宏来实现相同效果:
PyEval_InitThreads(); Py_BEGIN_ALLOW_THREADS // 宏会自动释放GIL,后续主线程代码将在无GIL状态下运行 Py_END_ALLOW_THREADS
修改后的完整代码示例
#include <iostream> #include <taskflow/taskflow.hpp> // Taskflow is header-only #define PY_SSIZE_T_CLEAN #include <Python.h> int main(int argc, char* argv[]) { wchar_t pythonHome[] = L".venv"; Py_SetPythonHome(pythonHome); Py_Initialize(); PyEval_InitThreads(); // 释放主线程GIL,让工作线程可正常获取 PyEval_ReleaseThread(PyThreadState_Get()); if (!Py_IsInitialized()) { std::cerr << "Python failed to initialize\n"; return 1; } // Set up Python paths PyRun_SimpleString( "import sys\n" "sys.path.insert(0, '.venv/Lib')\n" "sys.path.insert(0, '.venv/Lib/site-packages')\n" ); // Test script execution PyRun_SimpleString( "from time import time, ctime\n" "print('Today is', ctime(time()))\n" ); PyObject* main = PyImport_AddModule("__main__"); PyObject* globals = PyModule_GetDict(main); tf::Executor executor; tf::Taskflow taskflow; auto [A, B, C, D] = taskflow.emplace( // create four tasks [] () { std::cout << "TaskA\n"; // 正确成对管理GIL PyGILState_STATE gstate = PyGILState_Ensure(); // 这里可添加具体Python操作(如调用函数、创建对象等) PyGILState_Release(gstate); }, [] () { std::cout << "TaskB\n"; }, [] () { std::cout << "TaskC\n"; }, [] () { std::cout << "TaskD\n"; } ); A.precede(B, C); // A runs before B and C D.succeed(B, C); // D runs after B and C executor.run(taskflow).wait(); // 终结Python前,主线程重新获取GIL确保清理正常 PyGILState_STATE gstate = PyGILState_Ensure(); if (Py_FinalizeEx() < 0) { return 120; } PyGILState_Release(gstate); return 0; }
额外注意事项
- GIL管理规范:所有非Python创建的C++线程,只要调用Python API,必须用
PyGILState_Ensure()获取GIL,用PyGILState_Release()释放,确保成对调用。 - Python终结注意:调用
Py_FinalizeEx()前,要确保所有线程都已释放GIL,建议在主线程重新获取GIL后再执行终结操作,避免崩溃或资源泄漏。 - sys.path优化:如果程序工作目录可能变化,建议用绝对路径设置sys.path,或通过Python的
site模块自动处理,避免找不到模块的问题。
这样修改后,TaskA的工作线程就能顺利获取GIL,不会再出现挂死的情况了。
内容来源于stack exchange
相关产品推荐
相关产品推荐

