从C语言调用Py_Finalize()函数

13

这是对Call Python from C++的后续。

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

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()时,我遇到了一个段错误。

void exitPython(){
    PyEval_AcquireLock();
    Py_Finalize();
}
参考文献对于Py_Finalize()的解释有些模糊(或者我可能只是理解错了),我不确定在有一些活动线程时PyEval_AcquireLock()能否获取锁,以及如果在调用Py_Finalize()时有活动线程会发生什么。无论如何,即使我确定所有线程都已完成工作,只要至少创建了一个线程,就会出现段错误。例如,调用initPython()后跟exitPython()不会创建任何错误。我可以忽略这个问题并希望操作系统知道它在做什么,但我更希望能够弄清楚发生了什么。

很好的问题。如果你有时间,也许你可以帮我解决一个类似的问题:https://stackoverflow.com/questions/59959303/calling-py-initialize-py-finalize-in-c-twice-leads-to-package-errors - gingras.ol
3个回答

7

整个部分都有些可疑,但我想我已经找到了我的错误。

在初始化解释器时,我必须保存PyThreadState,并在完成后将此状态切换回去(不知道为什么需要特定的ThreadState来调用Finalize - 难道每个State都可以吗?)

无论如何,以下是其他人遇到相同问题的示例:

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()会发生什么。听起来这是竞态条件的完美例子...

1

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

如果一次只运行一个脚本,则没有问题。挂钩会被正确释放,但是如果并行运行两个或更多脚本,则挂钩不会停止。虽然我的脚本已经正确返回,并且pyxhook中的cancel()也已经正确返回,但我认为仍然有一些与xlib相关的线程正在运行。我通过保持全局标志来解决这个pyxhook问题,以便监视是否已经运行pyxhook,并且不要从每个线程重新初始化pyxhook

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

如果在调用Py_Finalize()之前不调用PyEval_AcquireLock()PyThreadState_Swap(),则它会在Linux中终止,但在Win32中不会。如果我不经过PyEval_AcquireLock()PyThreadState_Swap()就进行操作,则在Win32中存在问题。

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


1
你尝试过注释掉线程中所有的“工作”代码吗?把它替换成繁忙循环或者睡眠之类的东西。这样可以帮助你确定是初始化/关闭代码问题,还是Python在中间执行的实际操作问题。也许你没有正确设置线程 - 在C API中有很多针对线程的特定函数,我不确定你需要哪些来确保正常运行。

我已经将除PyGILState_Ensure()和Release()之外的所有内容注释掉了,但错误仍然发生。 如果我也将它们注释掉,就没有问题了。 - Voo
在这种情况下,我猜测线程管理方面存在一些问题。不幸的是,包含所有线程函数的相关C API页面并不明显,你需要知道哪些调用才是必要的。 - Kylotan
大多数情况下,你需要的“调用”是 Py_BEGIN_ALLOW_THREADS 及其对应项。这些又是使用 PyEval_SaveThread() 及其对应项的宏。因此,如果我要编写类似 OP 的东西,我会遵循这个例子。 - Kevin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接