Cython, Python and KeyboardInterrupt ignored



是否有一种方法可以中断(Ctrl+C)基于嵌入在Cython扩展中的循环的Python脚本?

我有以下python脚本:

def main():
    # Intantiate simulator
    sim = PySimulator()
    sim.Run()
if __name__ == "__main__":
    # Try to deal with Ctrl+C to abort the running simulation in terminal
    # (Doesn't work...)
    try:
        sys.exit(main())
    except (KeyboardInterrupt, SystemExit):
        print 'n! Received keyboard interrupt, quitting threads.n'

这将运行一个循环,它是c++ Cython扩展的一部分。然后,当按下Ctrl+C时,抛出KeyboardInterrupt但忽略,程序继续运行,直到模拟结束。

我发现的工作是通过捕获SIGINT信号来处理扩展内的异常:
#include <execinfo.h>
#include <signal.h>
static void handler(int sig)
{
  // Catch exceptions
  switch(sig)
  {
    case SIGABRT:
      fputs("Caught SIGABRT: usually caused by an abort() or assert()n", stderr);
      break;
    case SIGFPE:
      fputs("Caught SIGFPE: arithmetic exception, such as divide by zeron",
            stderr);
      break;
    case SIGILL:
      fputs("Caught SIGILL: illegal instructionn", stderr);
      break;
    case SIGINT:
      fputs("Caught SIGINT: interactive attention signal, probably a ctrl+cn",
            stderr);
      break;
    case SIGSEGV:
      fputs("Caught SIGSEGV: segfaultn", stderr);
      break;
    case SIGTERM:
    default:
      fputs("Caught SIGTERM: a termination request was sent to the programn",
            stderr);
      break;
  }
  exit(sig);
}

:

signal(SIGABRT, handler);
signal(SIGFPE,  handler);
signal(SIGILL,  handler);
signal(SIGINT,  handler);
signal(SIGSEGV, handler);
signal(SIGTERM, handler);

我不能让这个工作从Python,或至少从Cython代替?由于我即将在Windows/MinGW下移植我的扩展,我希望有一些不那么特定于Linux的东西。

您必须定期检查挂起的信号,例如,在模拟循环的每n次迭代中:

from cpython.exc cimport PyErr_CheckSignals
cdef Run(self):
    while True:
        # do some work
        PyErr_CheckSignals()

PyErr_CheckSignals将运行安装了信号模块的信号处理程序(这包括在必要时提升KeyboardInterrupt)。

PyErr_CheckSignals非常快,可以经常调用它。请注意,它应该从主线程调用,因为Python在主线程中运行信号处理程序。从工作线程调用它没有效果。

由于信号是在不可预测的时间异步传递的,因此直接从信号处理程序运行任何有意义的代码都是有问题的。因此,Python对传入信号进行排队。该队列稍后作为解释器循环的一部分进行处理。

如果你的代码完全编译,解释器循环永远不会执行,Python也没有机会检查和运行排队的信号处理程序。

如果您试图在释放GIL的代码中处理KeyboardInterrupt(例如,因为它使用cython.parallel.prange),则需要重新获取GIL以调用PyErr_CheckSignals。下面的代码片段(改编自@nikita-nemkin的回答)说明了您需要做的事情:

from cpython.exc cimport PyErr_CheckSignals
from cython.parallel import prange
cdef Run(self) nogil:
    with nogil:
        for i in prange(1000000)
            # do some work but check for signals every once in a while
            if i % 10000 == 0:
                with gil:
                    PyErr_CheckSignals()

在Cython运行不与Python接口的部分时释放GIL,在主线程中运行循环(睡眠或检查模拟状态),并在except close中调用sim.Stop()(可以设置一些模拟可以定期检查的标志)。

是的,使用包cysignals中的宏sig_onsig_off:

from cysignals.signals cimport sig_on, sig_off
def foo():
    sig_on()
    call_c_code_that_takes_long()
    sig_off()

sig_onsig_offcysignals/signals.pxd中声明为函数,在cysignals/macros.h中通过宏_sig_on_(通过函数_sig_on_prejmp_sig_on_postjmp定义)和函数_sig_off_定义为宏。这里安装了键盘中断的信号处理程序(SIGINT),这里概述了实现原理。

cysignals == 1.6.5开始,只支持POSIX系统。Cython的条件编译可以在任何cysignals可用的地方使用,并且也允许在非posix系统上编译(在这些系统上没有Ctrl-C工作)。

脚本setup.py:

compile_time_env = dict(HAVE_CYSIGNALS=False)
# detect `cysignals`
if cysignals is not None:
    compile_time_env['HAVE_CYSIGNALS'] = True
...
c = cythonize(...,
              compile_time_env=compile_time_env)

和相关的*.pyx文件:

IF HAVE_CYSIGNALS:
    from cysignals.signals cimport sig_on, sig_off
ELSE:
    # for non-POSIX systems
    noop = lambda: None
    sig_on = noop
    sig_off = noop

相关内容

  • 没有找到相关文章

最新更新