Python/C API调用含scipy.optimize.least_squares的Python函数时挂起
问题:C++通过Python/C API调用含scipy导入的Python函数时挂起
问题描述
- 在C++中使用Python/C API调用Python函数,只要被调用的Python代码执行
from scipy.optimize import least_squares就会挂起,哪怕并未调用least_squares函数本身。 - 单独编写的测试程序可正常运行,但将代码集成到大型程序中后出现挂起问题。
- GDB调试发现:测试程序会启动十几个C/Python API相关线程,而大型程序中未启动这些线程;中断后显示挂起在获取GIL(全局解释器锁)的环节,调用栈如下。
C++调用代码
PyObject* pTestModuleName = PyUnicode_FromString( "test_clib" ); PyObject* pTestModule = PyImport_Import( pTestModuleName ); PyObject* pFunction = PyObject_GetAttrString( pTestModule, "testfunc1" ); const char* str = "foo"; PyObject* pArgList = Py_BuildValue( "(s)", str ); PyObject* pKeywords = PyDict_New(); PyDict_SetItemString( pKeywords, "bar", Py_True ); PyObject* pReturn = PyObject_Call( pFunction, pArgList, pKeywords );
GDB调试调用栈
^C Thread 18 "acamd" received signal SIGINT, Interrupt.0x00007ffff73ca7e8 in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0 (gdb) where #0 0x00007ffff73ca7e8 in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib6/libpthread.so.0 #1 0x00007ffff793bef5 in take_gil () from /lib64/libpython3.9.so.1.0 #2 0x00007ffff793c112 in PyEval_RestoreThread () from /lib64/libpython3.9.so.1.0 #3 0x00007ffff7a5dd8c in PyGILState_Ensure () from /lib64/libpython3.9.so.1.0 #4 0x00007fffb83bd6c5 in pybind11::detail::get_internals() () from /usr/local/lib64/python3.9/site-packages/scipy/spatial/_distance_pybind.cpython-39-x86_64-linux-gnu.so #5 0x00007fffb83ae22c in PyInit__distance_pybind () from /usr/local/lib64/python3.9/site-packages/scipy/spatial/_distance_pybind.cpython-39-x86_64-linux-gnu.so #6 0x00007ffff7a765bc in _imp_create_dynamic () from /lib64/libpython3.9.so.1.0 #7 0x00007ffff7989ac8 in cfunction_vectorcall_FASTCALL () from /lib6/libpython3.9.so.1.0 #8 0x00007ffff799a8eb in PyObject_Call () from /lib64/libpython3.9.so.1.0 #9 0x00007ffff7a089d8 in _PyEval_EvalFrameDefault () from /lib64/libpython3.9.so.1.0 #10 0x00007ffff79f084e in _PyEval_EvalCode () from /lib64/libpython3.9.so.1.0 #11 0x00007ffff79f1e2b in _PyFunction_Vectorcall () from /lib64/libpython3.9.so.1.0 : : :
解决方案
1. 严格规范GIL管理
大型多线程程序中,Python/C API的GIL操作必须遵循规则:
- 调用任何Python API前,通过
PyGILState_Ensure()获取GIL;调用完成后,用PyGILState_Release()释放。 - 排查大型程序中是否存在其他线程持有GIL未释放的情况,导致当前线程无法获取锁。
2. 初始化Python时启用线程支持
确保在初始化Python环境时,开启线程支持:
Py_SetProgramName(argv[0]); // 建议设置,确保Python路径正确 Py_Initialize(); PyEval_InitThreads(); // 初始化Python线程管理结构 PyEval_SaveThread(); // 释放主线程GIL,允许其他线程获取
如果未调用PyEval_InitThreads(),Python不会创建线程相关基础结构,scipy中依赖pybind11的扩展模块会因无法正常获取GIL而挂起。
3. 检查大型程序线程模型
scipy部分模块(如pybind11实现的扩展)依赖Python线程支持。若大型程序限制了线程创建(如禁用pthread),会导致Python无法启动必要线程,进而引发GIL死锁。
4. 延迟导入scipy模块
在Python函数内部延迟导入least_squares,避免模块初始化阶段的线程冲突:
def testfunc1(foo, bar=True): from scipy.optimize import least_squares # 后续业务代码
内容的提问来源于stack exchange,提问作者astronomerdave
相关产品推荐
相关产品推荐

