停止嵌入式Python
我正在把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 个回答
好吧,Python解释器必须在一个和嵌入程序不同的线程中运行,否则你根本没有机会去打断这个解释器。
当你做到这一点后,也许你可以使用Python的API中的一些异常调用来在解释器中触发一个异常?可以查看一下Python C API异常。
我想要能够中断任何正在运行的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()
你可以使用 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语言的函数可能会作为一个单独的操作运行一段时间(之前有个例子是关于长时间运行的正则表达式搜索,但我不确定这个例子现在是否还适用)。