【问题标题】:Cython, Python and KeyboardInterrupt ignoredCython、Python 和 KeyboardInterrupt 被忽略
【发布时间】:2013-05-27 09:12:53
【问题描述】:

有没有办法根据嵌入在 Cython 扩展中的循环中断 (Ctrl+C) 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 zero\n",
            stderr);
      break;
    case SIGILL:
      fputs("Caught SIGILL: illegal instruction\n", stderr);
      break;
    case SIGINT:
      fputs("Caught SIGINT: interactive attention signal, probably a ctrl+c\n",
            stderr);
      break;
    case SIGSEGV:
      fputs("Caught SIGSEGV: segfault\n", stderr);
      break;
    case SIGTERM:
    default:
      fputs("Caught SIGTERM: a termination request was sent to the program\n",
            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 的东西。

【问题讨论】:

    标签: python cython keyboardinterrupt


    【解决方案1】:

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

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

    PyErr_CheckSignals 将运行与signal 模块一起安装的信号处理程序(这包括在必要时提高KeyboardInterrupt)。

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

    说明

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

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

    【讨论】:

    • +1 进行解释。 PyErr_CheckSignals() 是否在 Windows 上处理 Ctrl-C?只要另一个线程中的 Cython 代码定期释放 GIL,我就选择退出主线程中 while sim.running: time.sleep(1) 循环的隐式信号检查。
    • 我用错了"opt out"。我的意思正好相反:s/opt out/choose to do/ 以上。
    • 谢谢。我试一试。并且仍然希望对其进行基准测试,因为性能在我的模拟中非常重要(可能需要数小时甚至数天)。但这似乎是 A 梦寐以求的解决方案……
    • 这是用 Cython 编写的任何长时间运行的循环所必需的,还是只需要调用外部 C 函数的循环?
    【解决方案2】:

    如果您尝试在释放 GIL 的代码中处理 KeyboardInterrupt(例如,因为它使用了 cython.parallel.prange),则需要重新获取 GIL 以调用 PyErr_CheckSignals。以下 sn-p (改编自上面@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()
    

    【讨论】:

    • 除非你在主线程中,否则调用 PyErr_CheckSignals 是没有意义的。
    【解决方案3】:

    当 Cython 运行不与 Python 接口的部分时释放 GIL,在主线程中运行循环(休眠或检查模拟状态),并在except 关闭。

    【讨论】:

    • 好吧,这听起来很有意义(即使我偷偷摸摸,也会有一个轻松的解决方案 ;-)。感谢您的建议。
    【解决方案4】:

    是的,使用 cysignals 包中的宏 sig_on and sig_off

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

    sig_onsig_off被声明为函数in cysignals/signals.pxd,并根据宏_sig_on_定义为宏in cysignals/macros.h(根据函数_sig_on_prejmp_sig_on_postjmp定义)和函数_sig_off_。键盘中断的信号处理程序(SIGINT)安装在here,实现原理在here中概述。

    截至cysignals == 1.6.5,仅支持 POSIX 系统。 Cython 的conditional compilation 可以用于在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
    

    另见this answer

    【讨论】:

      猜你喜欢
      • 2011-04-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多