允许Ctrl-C中断Python C扩展

24 投票
5 回答
6997 浏览
提问于 2025-04-17 14:48

我在用自己写的C语言扩展做一些计算量很大的模拟。有时候我会出错,想要终止这个模拟。但是,按Ctrl-C似乎没有任何效果(除了在屏幕上显示^C),所以我只能用kill命令或者系统监视器来强制结束进程。

我观察到,Python在等待C扩展完成时,并没有真正和它进行沟通。

有没有办法让这个过程正常工作呢?

更新: 针对我具体的问题,主要的解决方案是: 1. 重写代码,让它定期把控制权交还给调用者(可以参考下面的回答 允许Ctrl-C中断Python C扩展),或者 2. 使用PyErr_CheckSignals()(可以参考下面的回答 https://stackoverflow.com/a/33652496/423420

5 个回答

8

在Python中,有一个信号处理器专门用来处理信号,这个信号通常是通过按下Ctrl+C来发送的。这个处理器的作用就是设置一个标志,主解释器会定期检查这个标志。为了让这个处理器正常工作,Python解释器必须在运行Python代码。

你有几种选择可以使用:

  1. 可以使用Py_BEGIN_ALLOW_THREADSPy_END_ALLOW_THREADS来释放全局解释器锁(GIL),这样你的C扩展代码就可以在不被锁住的情况下运行。虽然在没有持有GIL的情况下不能调用任何Python函数,但Python代码和其他C代码可以和你的C线程同时运行,这就是真正的多线程。这样,一个独立的Python线程可以和C扩展一起运行,并捕捉到Ctrl+C信号。
  2. 你可以设置自己的处理器,并在里面调用原来的(Python的)信号处理器。这样,你的处理器就可以做它需要做的事情,比如取消C扩展代码的执行,并把控制权交回给Python解释器。
21

不过,按下 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

相关内容: Cython、Python 和 KeyboardInterrupt 被忽略

0

我会重新设计C语言的扩展,让它们运行的时间不要太长。

也就是说,把它们拆分成更简单的步骤(每个步骤运行的时间很短,比如10到50毫秒),然后让这些简单的步骤通过Python代码来调用。

了解一下继续传递风格可能会有帮助,因为这是一种编程风格……

撰写回答