允许Ctrl-C中断Python C扩展
我在用自己写的C语言扩展做一些计算量很大的模拟。有时候我会出错,想要终止这个模拟。但是,按Ctrl-C似乎没有任何效果(除了在屏幕上显示^C
),所以我只能用kill
命令或者系统监视器来强制结束进程。
我观察到,Python在等待C扩展完成时,并没有真正和它进行沟通。
有没有办法让这个过程正常工作呢?
更新: 针对我具体的问题,主要的解决方案是:
1. 重写代码,让它定期把控制权交还给调用者(可以参考下面的回答 允许Ctrl-C中断Python C扩展),或者
2. 使用PyErr_CheckSignals()
(可以参考下面的回答 https://stackoverflow.com/a/33652496/423420)
5 个回答
在Python中,有一个信号处理器专门用来处理
你有几种选择可以使用:
- 可以使用
Py_BEGIN_ALLOW_THREADS
和Py_END_ALLOW_THREADS
来释放全局解释器锁(GIL),这样你的C扩展代码就可以在不被锁住的情况下运行。虽然在没有持有GIL的情况下不能调用任何Python函数,但Python代码和其他C代码可以和你的C线程同时运行,这就是真正的多线程。这样,一个独立的Python线程可以和C扩展一起运行,并捕捉到Ctrl+C信号。 - 你可以设置自己的
处理器,并在里面调用原来的(Python的)信号处理器。这样,你的 处理器就可以做它需要做的事情,比如取消C扩展代码的执行,并把控制权交回给Python解释器。
不过,按下 Ctrl-C 似乎没有任何效果。
Ctrl-C
在命令行中会向前台进程组发送 SIGINT
信号。当 python
收到这个信号时,会在 C 代码中设置一个标志。如果你的 C 扩展在主线程中运行,那么 Python 的信号处理程序就不会被执行(所以你不会在按下 Ctrl-C
时看到 KeyboardInterrupt
异常),除非你调用 PyErr_CheckSignals()
来检查这个标志(这意味着:这不会影响你的速度),并在必要时运行 Python 的信号处理程序,或者如果你的模拟允许 Python 代码执行(例如,如果模拟使用了 Python 的回调函数)。这里有一个 使用 pybind11 创建的 CPython 扩展模块的代码示例,建议由 @Matt 提供:
PYBIND11_MODULE(example, m)
{
m.def("long running_func", []()
{
for (;;) {
if (PyErr_CheckSignals() != 0)
throw py::error_already_set();
// Long running iteration
}
});
}
如果扩展在后台线程中运行,那么只需要释放全局解释器锁(GIL),这样就可以让 Python 代码在主线程中运行,从而使信号处理程序能够执行。在后台线程中,PyErr_CheckSignals()
总是返回 0
。
我会重新设计C语言的扩展,让它们运行的时间不要太长。
也就是说,把它们拆分成更简单的步骤(每个步骤运行的时间很短,比如10到50毫秒),然后让这些简单的步骤通过Python代码来调用。
了解一下继续传递风格可能会有帮助,因为这是一种编程风格……