停止嵌入式Python

27
我在将Python解释器嵌入到C程序中时,通过PyRun_SimpleString()运行某些Python脚本时可能会遇到无限循环或执行时间过长的情况。例如:PyRun_SimpleString("while 1: pass"); 为了防止主程序被阻塞,我想在线程中运行解释器。
如何在不杀死整个进程的情况下停止运行在线程中的Python脚本?
是否可以向解释器传递异常?我应该将脚本包装在监听信号的其他脚本下吗?
PS:我可以在一个单独的进程中运行Python,但这不是我想要的 - 除非这是最后的手段...
更新:
现在它可以工作了。再次感谢Denis Otkidach!
如果我理解得正确,您需要做两件事:在运行PyRun_SimpleString()的同一线程中告诉解释器停止,并返回-1。
要停止,有几种可能性:使用PyErr_SetString(PyExc_KeyboardInterrupt, "...")PyErr_SetInterrupt() - 第一个方法可能会让Python运行更多指令然后停止,后者会立即停止执行。
要返回-1,您可以使用Py_AddPendingCall()将一个函数调用注入到Python执行中。文档从2.7和3.1版本开始提到了它,但在较早的Python版本中(如2.6)也可以运行。从2.7和3.1开始,它还应该是线程安全的,这意味着您可以在不获取GIL的情况下调用它(?)。
因此,可以重写下面的示例:
int quit() {
    PyErr_SetInterrupt();
    return -1;
}
3个回答

13

您可以使用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函数可能作为单个操作运行一段时间(有一个长时间运行的正则表达式搜索示例,但我不确定它是否仍然相关)。


这看起来非常有趣,但它不是为最新的Python设计的吗?(2.7 alpha刚刚发布。)然而,我可以升级到Python 3.1 - 但那又怎样呢?我应该通过Py_AddPendingCall()注册哪个函数?PyErr_SetString()Py_Exit()?还是Py_Finalize() - Anton L.
Py_AddPendingCall至少从Python 2.0版本开始存在(在ceval.h中定义)。我已经在我的答案中添加了一个示例代码。 - Denis Otkidach
在我的应用程序中,支持执行任意用户脚本的Python插件,调用Py_Exit(0)不是解决方案,因为我不想拆除整个Python解释器。相反,我只想中断正在执行的脚本。在这种情况下,使用PyErr_SetString(PyExc_Exception, "Reset!");而不是Py_Exit(0)退出了用户的脚本。 - Mark Coniglio

2
我希望能够中断任何正在运行的Python代码块,包括取消无限循环或一些长时间运行的代码。在我的应用程序中,单线程Python代码被提交和评估。中断请求可以随时发生。这个问题和答案对我实际实现这一点非常关键。
看着这个问题,在2019年,这里的解决方案对我没有用;我试图以多种不同的方式使用Py_AddPendingCall(&quit, NULL);,但调用的函数从未按预期执行,或者根本没有执行。当我提交Py_FinalizeEx()时,这导致Python引擎挂起并最终崩溃。我最终在这个非常有用的答案中找到了我需要的东西。
在启动Python引擎后,我使用Python代码中的threading包检索线程ID。我将其解析为c长值并保存它。
中断函数实际上非常简单:
PyGILState_Ensure()
PyThreadState_SetAsyncExc(threadId, PyExc_Exception)使用我之前保存的线程ID和通用异常类型
PyGILState_Release()

这似乎不是对该问题的答案,而是一个跟进问题,并在一个不必要的轶事中解决了该问题。SO不是一个论坛。请仅使用回答来回答所提出的问题。如果您认为您的新问题和解决方案对他人有用,则可以发布一个新问题,在那里引用此问题并描述新问题,然后将您的解决方案作为答案发布。 - Max Vollmer
2
我正在回答一个问题:“如何在运行在线程中的嵌入式解释器中停止执行Python脚本而不杀死整个进程?”。原始的被接受的答案对我没有用,但是我找到了另一种解决方法,所以我提供了关于另一种解决方案的信息。 - marmot 1333

1

好的,Python解释器必须在嵌入程序中运行在一个单独的线程中,否则你将永远没有机会中断解释器。

当你有了这个之后,也许你可以使用Python API异常调用之一来触发解释器中的异常?请参见Python C API Exceptions


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