You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

嵌入式Python 3.10中Py_FinalizeEx在threading._shutdown()处挂起死锁

C++嵌入Python时Py_FinalizeEx死锁问题

在C++应用中嵌入Python,怀疑对PyGILState_Ensure/PyGILState_Release的使用存在误区,最终导致Py_FinalizeEx在threading._shutdown()(由Py_FinalizeEx调用)中因线程join()操作而挂起死锁。

初始化代码:

Py_InitializeEx(0); // Skip installing signal handlers
auto gil = PyGILState_Ensure();
// ... running Python code
PyGILState_Release(gil);

C++线程调用Python的宏:

#define pyscope() \
    PyGILState_STATE gstate = PyGILState_Ensure(); \
    utils::scope_guard sggstate([&]() \
    { \
        PyGILState_Release(gstate); \
    });

释放资源的代码(死锁发生处):

PyGILState_STATE gstate = PyGILState_Ensure();
int res = Py_FinalizeEx(); // <--- HANGS!!!

调试后发现问题出在线程join阶段,可通过在Py_FinalizeEx前执行以下Python代码复现死锁:

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的获取与释放。

请问该死锁的原因是什么?如何解决?


原因分析

死锁的核心原因是调用Py_FinalizeEx时还持有GIL:Py_FinalizeEx会触发threading._shutdown(),该函数会尝试join所有存活的Python线程。而这些Python线程在启动后,执行逻辑中需要获取GIL才能继续运行或退出;此时你当前调用Py_FinalizeEx的线程牢牢持有GIL,导致那些Python线程永远无法获取GIL完成收尾,当前线程又在等待它们join,形成双向阻塞的死锁。

关于PyEval_SaveThread/RestoreThread:PyGILState_Ensure内部已经封装了PyEval_RestoreThread的逻辑,PyGILState_Release则对应PyEval_SaveThread,所以你不需要额外调用这两个函数,当前的pyscope宏写法本身没有问题。

解决方法

  1. 调用Py_FinalizeEx前必须释放GIL
    这是解决死锁最关键的一步,修改释放资源的代码:

    // 如果需要执行Python层面的清理操作,先获取GIL
    PyGILState_STATE gstate = PyGILState_Ensure();
    // 执行必要的清理(比如通知Python后台线程退出)
    PyGILState_Release(gstate); // 必须释放GIL后再调用Py_FinalizeEx
    int res = Py_FinalizeEx();
    

    释放GIL后,等待退出的Python线程才能获取到GIL完成收尾工作,threading._shutdown()的join操作才能正常完成。

  2. 提前主动终止所有Python后台线程
    不要依赖threading._shutdown()的强制join,在清理阶段主动通知所有Python后台线程退出。比如可以设置一个全局的退出标志变量,让Python线程在逻辑中定期检查该标志,主动退出执行流程。

  3. 确保所有C++线程已完成Python调用
    检查所有使用pyscope宏的C线程,确保它们都已经执行完Python调用逻辑并释放了GIL,避免在Py_FinalizeEx执行期间,还有C线程尝试获取GIL,引发额外的锁冲突。

  4. 遵守Py_FinalizeEx的调用规范
    Py_FinalizeEx是全局资源清理函数,执行时不能持有任何Python相关的锁(包括GIL),否则必然会与线程收尾逻辑产生死锁冲突。这是嵌入Python开发中必须牢记的原则。


内容的提问来源于stack exchange,提问作者TCS

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 19:00:13