18.8. signal - 设置异步事件处理程序

此模块提供了在Python中使用信号处理程序的机制。

18.8.1. General rules

signal.signal()函数允许定义在接收到信号时执行的自定义处理程序。安装了少量默认处理程序:SIGPIPE被忽略(因此管道和套接字上的写入错误可以报告为普通Python异常),SIGINT被转换为KeyboardInterrupt异常。

特定信号的处理程序一旦设置,将保持安装状态,直到显式复位(不管底层实现如何,Python都会模拟BSD样式接口),除了SIGCHLD的处理程序底层实现。

18.8.1.1. Execution of Python signal handlers

Python信号处理程序不会在低级(C)信号处理程序内部执行。相反,低级信号处理程序在稍后的点(例如在下一个bytecode指令处)设置告知virtual machine执行相应的Python信号处理程序的标志。这有后果:

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

18.8.1.2. Signals and threads

Python信号处理程序总是在主Python线程中执行,即使信号在另一个线程中被接收。这意味着信号不能用作线程间通信的手段。您可以使用来自threading模块的同步原语。

此外,只有主线程允许设置一个新的信号处理程序。

18.8.2. Module contents

在版本3.5中更改了信号(SIG *),处理程序(SIG_DFLSIG_IGN)和sigmask(SIG_BLOCK SIG_UNBLOCKSIG_SETMASK)相关常数变成enumsgetsignal(), pthread_sigmask(), sigpending() and sigwait() functions return human-readable enums.

signal模块中定义的变量是:

signal.SIG_DFL

这是两个标准信号处理选项之一;它将简单地执行信号的默认功能。例如,在大多数系统上,SIGQUIT的默认操作是转储核心和退出,而SIGCHLD的默认操作是简单地忽略它。

signal.SIG_IGN

这是另一个标准信号处理程序,它将简单地忽略给定的信号。

SIG *

所有信号编号都用符号定义。例如,挂起信号定义为signal.SIGHUP;变量名称与C程序中使用的名称相同,如<signal.h>中所述。The Unix man page for ‘signal()‘ lists the existing signals (on some systems this is signal(2), on others the list is in signal(7)). 请注意,并非所有系统都定义相同的信号名称集;只有由系统定义的那些名称由此模块定义。

signal.CTRL_C_EVENT

对应于Ctrl+C击键事件的信号。此信号只能与os.kill()一起使用。

可用性:Windows。

版本3.2中的新功能。

signal.CTRL_BREAK_EVENT

对应于Ctrl+Break击键事件的信号。此信号只能与os.kill()一起使用。

可用性:Windows。

版本3.2中的新功能。

signal.NSIG

一个多于最高信号数的数。

signal.ITIMER_REAL

实时递减间隔定时器,并在到期时递送SIGALRM

signal.ITIMER_VIRTUAL

仅在进程正在执行时减少间隔定时器,并在到期时传递SIGVTALRM。

signal.ITIMER_PROF

递减间隔定时器,当进程执行时和系统代表进程执行时。与ITIMER_VIRTUAL相耦合,此定时器通常用于配置应用程序在用户和内核空间中花费的时间。SIGPROF在到期时交付。

signal.SIG_BLOCK

如何参数到pthread_sigmask()的可能值,表示要阻止信号。

版本3.3中的新功能。

signal.SIG_UNBLOCK

如何参数到pthread_sigmask()的可能值,表示要解除阻塞信号。

版本3.3中的新功能。

signal.SIG_SETMASK

如何参数到pthread_sigmask()的可能值,表示要替换信号掩码。

版本3.3中的新功能。

signal模块定义了一个例外:

exception signal.ItimerError

引发来自底层setitimer()getitimer()实现的错误信号。如果将无效的间隔定时器或负时间传递到setitimer(),则会发生此错误。此错误是OSError的子类型。

版本3.3中的新功能:此错误以前是IOError的子类型,现在是OSError的别名。

signal模块定义以下功能:

signal.alarm(time)

如果时间不为零,则此功能请求在时间秒内将SIGALRM信号发送到过程。任何先前安排的警报被取消(只能在任何时间安排一个警报)。返回的值是之前设置的任何警报发送之前的秒数。如果时间为零,则不计划报警,并取消任何预设报警。如果返回值为零,则当前未调度报警。(参见Unix手册页alarm(2)。)可用性:Unix。

signal.getsignal(signalnum)

返回信号signalnum的当前信号处理程序。返回的值可以是可调用的Python对象或特殊值signal.SIG_IGNsignal.SIG_DFLNone之一。这里,signal.SIG_IGN意味着信号先前被忽略,signal.SIG_DFL意味着处理信号的默认方式是先前使用的,并且None

signal.pause()

使该过程睡眠直到接收到信号;那么将调用适当的处理程序。不返回任何内容。不在Windows上。(请参见Unix手册页信号(2)。)

另见sigwait()sigwaitinfo()sigtimedwait()sigpending()

signal.pthread_kill(thread_id, signalnum)

将信号signalnum发送到线程thread_id,这是与调用者相同的进程中的另一个线程。目标线程可以执行任何代码(Python或不是)。但是,如果目标线程正在执行Python解释器,则Python信号处理程序将由主线程执行executed by the main thread因此,向特定Python线程发送信号的唯一一点是强制运行系统调用失败,并显示InterruptedError

使用threading.get_ident()threading.Thread对象的ident属性为thread_id

如果signalnum为0,则不发送信号,但仍然执行错误检查;这可以用于检查目标线程是否仍在运行。

可用性:Unix(有关详细信息,请参见手册页pthread_kill(3))。

另请参见os.kill()

版本3.3中的新功能。

signal.pthread_sigmask(how, mask)

获取和/或更改调用线程的信号掩码。信号掩码是当前为呼叫者阻止其传递的一组信号。将旧的信号掩码作为一组信号返回。

调用的行为取决于如何的值,如下所示。

  • SIG_BLOCK:阻塞信号集合是当前集合的共用体和掩码参数。
  • SIG_UNBLOCK掩码中的信号从当前阻塞信号集中删除。允许尝试解除未阻塞的信号。
  • SIG_SETMASK:阻塞信号集设置为掩码参数。

掩码是一组信号号码(例如,{signal.SIGINTsignal.SIGTERM})。使用范围(1, signal.NSIG)获取包含所有信号的完整掩码。

例如,signal.pthread_sigmask(signal.SIG_BLOCK, [])读取调用线程的信号掩码。

可用性:Unix。有关更多信息,请参阅手册页sigprocmask(3)pthread_sigmask(3)

另请参见pause()sigpending()sigwait()

版本3.3中的新功能。

signal.setitimer(which, seconds[, interval])

设置由指定的给定间隔定时器(signal.ITIMER_REALsignal.ITIMER_VIRTUALsignal.ITIMER_PROF(接受浮动,不同于alarm())之后,每隔间隔指定的间隔定时器可以通过将秒设置为零来清除。

当间隔定时器触发时,向处理发送信号。The signal sent is dependent on the timer being used; signal.ITIMER_REAL will deliver SIGALRM, signal.ITIMER_VIRTUAL sends SIGVTALRM, and signal.ITIMER_PROF will deliver SIGPROF.

旧值作为元组返回:(delay,interval)。

尝试传递无效间隔定时器将导致ItimerError可用性:Unix。

signal.getitimer(which)

返回由指定的给定间隔定时器的当前值,其中可用性:Unix。

signal.set_wakeup_fd(fd)

将唤醒文件描述器设置为fd当接收到信号时,信号号作为单字节写入fd。这可以由库用来唤醒轮询或选择呼叫,从而允许完全处理信号。

返回旧的唤醒fd。fd必须是非阻塞的。在调用poll之前由库删除任何字节或再次选择。

使用例如struct.unpack('%uB' len(data), data) 来解码信号编号列表。

当启用线程时,此函数只能从主线程调用;尝试从其他线程调用它将导致引发ValueError异常。

在3.5版本中更改:在Windows上,此函数现在还支持套接字句柄。

signal.siginterrupt(signalnum, flag)

更改系统呼叫重新启动行为:如果标志False,系统呼叫将在由信号signalnum中断时重新启动,否则系统呼叫将中断。不返回任何内容。可用性:Unix(有关详细信息,请参见手册页siginterrupt(3))。

请注意,使用signal()安装信号处理程序将通过隐式调用siginterrupt()将真实的标志给定的信号。

signal.signal(signalnum, handler)

将信号signalnum的处理程序设置为处理程序处理程序可以是具有两个参数(见下文)或特殊值signal.SIG_IGNsignal.SIG_DFL的可调用Python对象。将返回上一个信号处理程序(请参阅上面的getsignal()的描述)。(请参见Unix手册页信号(2)。)

当启用线程时,此函数只能从主线程调用;尝试从其他线程调用它将导致引发ValueError异常。

使用两个参数调用处理程序:信号编号和当前堆栈帧(None或帧对象;有关帧对象的描述,请参阅description in the type hierarchy中,或查看inspect模块中的属性说明)。

On Windows, signal() can only be called with SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, or SIGTERM. 在任何其他情况下,将引发ValueError请注意,并非所有系统都定义相同的信号名称集;如果信号名称未定义为SIG*模块级常数,则会引发AttributeError

signal.sigpending()

检查等待传递到调用线程的信号集合(即,在阻塞时已经产生的信号)。返回待处理信号的集合。

可用性:Unix(有关详细信息,请参见手册页sigpending(2))。

另请参见pause()pthread_sigmask()sigwait()

版本3.3中的新功能。

signal.sigwait(sigset)

挂起调用线程的执行,直到发送信号集sigset中指定的信号之一。该函数接受信号(将其从待处理的信号列表中删除),并返回信号编号。

可用性:Unix(有关详细信息,请参见手册页sigwait(3))。

另请参见pause()pthread_sigmask()sigpending()sigwaitinfo()sigtimedwait()

版本3.3中的新功能。

signal.sigwaitinfo(sigset)

挂起调用线程的执行,直到发送信号集sigset中指定的信号之一。该函数接受信号并将其从待处理的信号列表中删除。如果sigset中的信号之一已经等待调用线程,则函数将立即返回有关该信号的信息。对于传递的信号,不调用信号处理程序。如果函数被不在sigset中的信号中断,则函数引发InterruptedError

返回值是表示包含在siginfo_t结构中的数据的对象,即:si_signosi_codesi_errnosi_pidsi_uidsi_statussi_band

可用性:Unix(有关详细信息,请参见手册页sigwaitinfo(2))。

另请参见pause()sigwait()sigtimedwait()

版本3.3中的新功能。

在版本3.5中更改:如果信号不在sigset中,信号处理程序不引发异常,则此函数现在重试(请参阅 PEP 475的理由)。

signal.sigtimedwait(sigset, timeout)

sigwaitinfo(),但是需要一个额外的超时参数指定超时。如果超时被指定为0,则执行轮询。如果发生超时,则返回None

可用性:Unix(有关详细信息,请参见手册页sigtimedwait(2))。

另请参见pause()sigwait()sigwaitinfo()

版本3.3中的新功能。

在版本3.5中更改:如果被不在sigset中的信号中断,并且信号处理程序未引发,则重新计算的超时异常(有关理由,请参见 PEP 475)。

18.8.3. Example

这里是一个最小的示例程序。它使用alarm()函数来限制等待打开文件所花费的时间;如果文件用于可能无法打开的串行设备,这通常会导致os.open()无限期挂起,这是非常有用的。解决方法是在打开文件之前设置5秒报警;如果操作花费太长时间,将发送报警信号,并且处理程序引发异常。

import signal, os

def handler(signum, frame):
    print('Signal handler called with signal', signum)
    raise OSError("Couldn't open device!")

# Set the signal handler and a 5-second alarm
signal.signal(signal.SIGALRM, handler)
signal.alarm(5)

# This open() may hang indefinitely
fd = os.open('/dev/ttyS0', os.O_RDWR)

signal.alarm(0)          # Disable the alarm