小能豆

嵌入式 Python (3.10) - Py_FinalizeEx 在“threading._shutdown()”上挂起/死锁

py

我正在将 Python 嵌入到 C++ 应用程序中,我认为我对此感到有些困惑,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);

每当一个线程使用 Python(可以是多个 C 线程)时,我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 之前运行)来重现死锁:

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。

你知道为什么会发生死锁以及如何解决这个问题吗?


阅读 29

收藏
2024-12-25

共1个答案

小能豆

这是因为每次获取 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;
}
2024-12-25