停止嵌入式Python

27 投票
3 回答
7271 浏览
提问于 2025-04-15 14:18

我正在把Python解释器嵌入到一个C程序中。不过,有时候在通过PyRun_SimpleString()运行某些Python脚本时,可能会遇到无限循环或者执行时间过长的情况。比如说,PyRun_SimpleString("while 1: pass");。为了防止主程序被阻塞,我想到了可以在一个线程中运行解释器。

那么,如何在不杀掉整个进程的情况下,停止在线程中运行的嵌入式Python脚本呢?

有没有办法给解释器传递一个异常?我是否应该把脚本放在另一个脚本里,这样它可以监听信号?

附注:我可以在一个单独的进程中运行Python,但这不是我想要的——除非这是最后的选择……


更新:

现在可以正常工作了。再次感谢Denis Otkidach!

如果我没理解错的话,你需要做两件事:告诉解释器停止,并且在和PyRun_SimpleString()运行在同一个线程中return -1

要停止,有几种方法:可以使用PyErr_SetString(PyExc_KeyboardInterrupt, "...")或者PyErr_SetInterrupt()——前者可能会让Python再执行几条指令后才停止,而后者会立即停止执行。

return -1,你可以使用Py_AddPendingCall()来在Python执行中插入一个函数调用。文档提到这个功能从2.7和3.1版本开始,但在早期的Python版本(比如2.6)也能运行。从2.7和3.1开始,它应该也是线程安全的,意味着你可以在不获取全局解释器锁(GIL)的情况下调用它(?)。

所以可以重写下面的示例:

int quit() {
    PyErr_SetInterrupt();
    return -1;
}

3 个回答

1

好吧,Python解释器必须在一个和嵌入程序不同的线程中运行,否则你根本没有机会去打断这个解释器。

当你做到这一点后,也许你可以使用Python的API中的一些异常调用来在解释器中触发一个异常?可以查看一下Python C API异常

2

我想要能够中断任何正在运行的Python代码块,包括取消无限循环或一些长时间运行的代码。在我的应用中,单线程的Python代码会被提交和评估。中断请求可以随时到来。这个问题和答案对我实现这个功能非常重要。

回到2019年看这个问题时,这里的解决方案对我来说并不奏效;我尝试了多种方式使用Py_AddPendingCall(&quit, NULL);,但被调用的函数从来没有按预期执行,甚至根本没有执行。这导致Python引擎卡住,最终在我提交Py_FinalizeEx()时崩溃。我最终在一个非常有用的答案中找到了我需要的内容,这个答案是针对类似问题的。

在我启动Python引擎后,我使用Python的threading包获取线程ID。然后我把它解析成一个c长整型值并保存下来。

中断函数其实很简单:
PyGILState_Ensure()
PyThreadState_SetAsyncExc(threadId, PyExc_Exception),使用我之前保存的线程ID和一个通用的异常类型
PyGILState_Release()

14

你可以使用 Py_AddPendingCall() 来添加一个函数,这个函数会在下一个检查间隔时被调用,并且会引发异常(想了解更多,可以查看 sys.setcheckinterval() 的文档)。下面是一个使用 Py_Exit() 的例子(这个对我来说是有效的,但可能不是你需要的),你可以把它换成 Py_Finalize() 或者 PyErr_Set*() 中的一个:

int quit(void *) {
    Py_Exit(0);
}


PyGILState_STATE state = PyGILState_Ensure();
Py_AddPendingCall(&quit, NULL);
PyGILState_Release(state);

这对于任何纯Python代码来说应该是足够的。但要注意,有些C语言的函数可能会作为一个单独的操作运行一段时间(之前有个例子是关于长时间运行的正则表达式搜索,但我不确定这个例子现在是否还适用)。

撰写回答