如何在Python中捕获阻塞boost c++方法中的中断信号?

How do I catch an Interrupt signal in Python when inside a blocking boost c++ method?

本文关键字:c++ 方法 中断 信号 boost Python      更新时间:2023-10-16

我有一个用c++编写的工具集,并为Python提供Boost绑定。

最初,这些代码都是c++,我捕获了一个CTRL+C中断:

signal( SIGINT, signalCallbackHandler );

void signalCallbackHandler(int /*signum*/)
{
    g_myObject->stop();
}

运行正常

然而,现在我已经添加了Python绑定,我正在使用Python来初始化对象。

我最初的想法是这样做:

import signal
def handle_interrupt( signum, frame ) :
    g_myObject.stop()
signal.signal( signal.SIGINT, handle_interrupt )
g_myObject = MyObject()
g_myObject.start()

但是,这个信号处理器永远不会被调用。

我应该如何处理这样的中断?我需要在c++中做吗,然后从那里调用Python函数?

你的python信号处理程序没有被调用,因为python将信号处理程序的执行延迟到下一个字节码指令执行之后-参见库文档中的信号,第18.8.1.1节:

Python信号处理程序不会在低级(C)信号处理程序中执行。相反,低级信号处理程序设置一个标志,该标志告诉虚拟机在稍后的点(例如在下一个字节码指令时)执行相应的Python信号处理程序。这会产生如下后果:

  • 在C代码中捕获由无效操作引起的同步错误(如SIGFPESIGSEGV)几乎没有意义。Python将从信号处理程序返回到C代码,这可能会再次引发相同的信号,导致Python显然挂起。从Python 3.3开始,你可以使用faulthandler模块来报告同步错误。
  • 一个完全用C实现的长时间运行的计算(比如在大量文本上的正则表达式匹配)可能会在任意时间内不间断地运行,而不管接收到任何信号。Python信号处理程序将在计算完成时被调用。

这样做的原因是信号可以在任何时候到达,可能在python指令执行的中途。VM开始执行信号处理程序是不安全的,因为VM处于未知状态。因此,python安装的实际信号处理程序只是设置一个标志,告诉VM在当前指令完成后调用信号处理程序。

如果信号在c++函数执行期间到达,那么信号处理程序设置标志并返回到你的c++函数。

如果信号处理程序的主要目的是允许c++函数被中断,那么我建议你放弃Python信号处理程序,并安装一个c++信号处理程序,该信号处理程序设置一个标志,在你的c++代码中触发提前退出(可能会返回一个表明它被中断的值)。

这种方法将允许您使用相同的代码,无论您是从python, c++还是其他绑定调用代码。

我有一个解决方案可以解决这个问题,尽管如果我可以用Python而不是c++捕获信号会更干净。

我之前没有提到的一件事是,MyObject是一个单例,所以我得到它与MyObject.getObject()

在Python中,我有:
def signalHandler( signum ) :
    if signum == signal.SIGINT :
        MyObject.getObject().stop()
def main() :
    signal.signal( signal.SIGINT, handle_interrupt )
    myObject = MyObject.getObject()
    myObject.addSignalHandler( signal.SIGINT, signalHandler )
    myObject.start()

在我的c++代码中,在一个不应该了解Python的地方,我有:

class MyObject
{
    public :
        void addSignalHandler( int signum, void (*handler)( int, void* ), void *data = nullptr );
        void callSignalHandler( int signum );
    private :
        std::map<int, std::pair<void (*)( int, void* ), void*> > m_signalHandlers;
}
void signalCallbackHandler( int signum )
{
    MyObject::getObject()->callSignalHandler( signum );
}
void MyObject::addSignalHandler( int signum, void (*handler)( int, void* ), void *data )
{
    m_signalHandlers.insert( std::pair<int, std::pair<void (*)( int, void* ), void *> >( signum, std::make_pair( handler, data ) ) );
    signal( signum, signalCallbackHandler );
}
void MyObject::callSignalHandler( int signum )
{
    std::map<int, std::pair<void (*)( int, void* ), void*> >::iterator handler = m_signalHandlers.find( signum );
    if( handler != m_signalHandlers.end() )
    {
        handler->second.first( signum, handler->second.second );
    }
}

然后在Python绑定中:

void signalHandlerWrapper( int signum, void *data )
{
    if( nullptr == data )
    {
        return;
    }
    PyObject *func = (PyObject*)( data );
    if( PyFunction_Check( func ) )
    {
        PyObject_CallFunction( func, "i", signum );
    }
}
void addSignalHandlerWrapper( MyObject *o, int signum, PyObject *func )
{
    Py_INCREF( func );
    if( PyFunction_Check( func ) )
    {
        o->addSignalHandler( signum, &signalHandlerWrapper, func );
    }
}

我没有的,我应该添加的是addSignalHandlerWrapper()中的一些东西,它将检查是否已经存在该信号号,如果是,获取它并在添加新信号之前减少引用。我还没有这样做,因为这个功能只用于结束程序,但为了完整性,应该把它放在适当的位置。

无论如何,正如我一开始所说的,这比它可能涉及的更多。它也只适用于我有一个可以跟踪函数指针的单例。