从C调用Py_Finalize()

13 投票
3 回答
5768 浏览
提问于 2025-04-15 14:20

这是对从C++调用Python问题的后续讨论。

在程序启动时,我调用以下函数来初始化解释器:

void initPython(){
    PyEval_InitThreads();
    Py_Initialize();
    PyEval_ReleaseLock();
}

每个线程都会创建自己的数据结构,并通过以下方式获取锁:

PyGILState_STATE gstate;
gstate = PyGILState_Ensure();
//call python API, process results
PyGILState_Release(gstate);

一旦你理解了全局解释器锁(GIL),这就相对简单了,但问题是当我调用Py_Finalize()时会出现段错误(segfault)。

void exitPython(){
    PyEval_AcquireLock();
    Py_Finalize();
}

关于Py_Finalize()的参考资料有点模糊(或者可能是我理解错了),我不确定如果有活动线程时,PyEval_AcquireLock()是否能成功获取锁,以及在调用Py_Finalize()时如果有活动线程会发生什么。

无论如何,即使我确定所有线程都已经完成工作,只要至少创建了一个线程,我就会遇到段错误。例如,调用initPython()后再调用exitPython()就不会出现错误。

我可以选择忽略这个问题,希望操作系统能处理好,但我更希望能弄清楚到底发生了什么……

3 个回答

1

我在运行包含 pyxhook 的脚本时,也遇到了类似的问题,这些脚本是通过嵌入式解释器从不同的线程中运行的。

如果一次只运行一个脚本,那就没有问题。这个钩子会正常释放,但如果同时运行两个或更多脚本,钩子就不会停止。虽然我的脚本正常返回,并且 pyxhookcancel() 也正常返回,但我觉得还是有一些与 xlib 相关的线程在运行。为了解决这个 pyxhook 的问题,我设置了一个全局标志来监控 pyxhook 是否已经在运行,并且不在每个线程中重新初始化 pyxhook

现在说到 Py_Finalize(),如果在每个线程中重新初始化 pyxhook

如果我在调用 Py_Finalize() 之前不调用 PyEval_AcquireLock()PyThreadState_Swap(),在 Linux 上会终止,但在 Win32 上就会有问题。如果我不通过 PyEval_AcquireLock()PyThreadState_Swap(),在 Win32 上会出现问题。

目前对我来说,临时解决方案是在两个不同的操作系统中以不同的方式终止。

1

你有没有试过把你线程里所有的“工作”都注释掉?可以用一个忙等循环或者让它睡一会儿来替代。这可以帮助你找出问题是出在初始化或关闭代码上,还是在你实际对Python做的事情上。也许你没有正确设置线程——在C语言的API里有很多和线程相关的函数,我不太确定你需要用哪些来确保它们正常运行。

7

是的,这一整段内容确实有点可疑,不过我想我找到了我的错误。

我需要在初始化解释器的时候保存PyThreadState,然后在结束的时候再把这个状态换回来(我不知道为什么调用Finalize时需要一个特定的ThreadState,难道不是任何状态都可以吗?)

无论如何,如果其他人遇到同样的问题,这里有个例子:

PyThreadState *mainstate;

void initPython(){
    PyEval_InitThreads();
    Py_Initialize();
    mainstate = PyThreadState_Swap(NULL);
    PyEval_ReleaseLock();
}

void exitPython(){
    PyEval_AcquireLock();
    PyThreadState_Swap(mainstate);
    Py_Finalize();
}

唯一的问题是,我可以像其他线程一样获取锁,即使还有其他线程在工作。 这个API没有说明当Finalize()被调用时,如果还有其他线程在工作会发生什么。这听起来就像是一个典型的竞争条件的例子……

撰写回答