信号 — 为异步事件设置处理程序 — Python 文档
signal — 为异步事件设置处理程序
该模块提供了在 Python 中使用信号处理程序的机制。
通用规则
signal.signal() 函数允许定义在接收到信号时执行的自定义处理程序。 安装了少量默认处理程序:SIGPIPE 被忽略(因此管道和套接字上的写入错误可以报告为普通 Python 异常)并且 SIGINT 被翻译成 KeyboardInterrupt 异常,如果父进程没有改变它。
一个特定信号的处理程序,一旦设置,就会保持安装状态,直到它被显式重置(Python 模拟 BSD 风格的接口,而不管底层实现如何),除了 SIGCHLD 的处理程序,它遵循底层执行。
Python 信号处理程序的执行
Python 信号处理程序不会在低级 (C) 信号处理程序内执行。 相反,低级信号处理程序设置一个标志,告诉 虚拟机 在稍后执行相应的 Python 信号处理程序(例如在下一个 bytecode 指令)。 这有以下后果:
- 捕获由 C 代码中的无效操作引起的同步错误(如 SIGFPE 或 SIGSEGV)毫无意义。 Python 会从信号处理程序返回到 C 代码,这很可能再次引发相同的信号,导致 Python 明显挂起。 从 Python 3.3 开始,您可以使用 faulthandler 模块报告同步错误。
- 纯粹用 C 实现的长时间运行的计算(例如在大量文本上进行正则表达式匹配)可能会不间断地运行任意时间,而不管接收到任何信号。 计算完成后将调用 Python 信号处理程序。
信号和线程
Python 信号处理程序始终在主解释器的主 Python 线程中执行,即使信号是在另一个线程中接收到的。 这意味着信号不能用作线程间通信的手段。 您可以改用 threading 模块中的同步原语。
此外,只允许主解释器的主线程设置新的信号处理程序。
模块内容
3.5 版更改: 信号 (SIG*)、处理程序 (SIG_DFL、SIG_IGN) 和信号掩码 (SIG_BLOCK、SIG_UNBLOCK) , SIG_SETMASK) 相关的常量被转换成 枚举。 getsignal()、pthread_sigmask()、sigpending() 和 sigwait() 函数返回人类可读的 枚举 ]。
signal模块中定义的变量是:
- signal.SIG_DFL
- 这是两个标准信号处理选项之一; 它将简单地执行信号的默认功能。 例如,在大多数系统上,
SIGQUIT
的默认操作是转储核心并退出,而 SIGCHLD 的默认操作是简单地忽略它。
- signal.SIG_IGN
- 这是另一个标准信号处理程序,它将简单地忽略给定的信号。
- signal.SIGABRT
- 来自 abort(3) 的中止信号。
- signal.SIGALRM
- 来自 警报 (2) 的定时器信号。
- signal.SIGBREAK
- 从键盘中断(CTRL + BREAK)。
- signal.SIGBUS
- 总线错误(内存访问错误)。
- signal.SIGCHLD
- 子进程停止或终止。
- signal.SIGCLD
- SIGCHLD 的别名。
- signal.SIGCONT
- 如果当前已停止,则继续该过程
- signal.SIGFPE
浮点异常。 例如,除以零。
也可以看看
ZeroDivisionError 当除法或模运算的第二个参数为零时引发。
- signal.SIGHUP
- 检测到控制终端挂断或控制进程死亡。
- signal.SIGILL
- 非法指示。
- signal.SIGINT
从键盘中断 (CTRL + C)。
默认操作是引发 KeyboardInterrupt。
- signal.SIGKILL
杀信号。
它不能被捕获、阻止或忽略。
- signal.SIGPIPE
管道损坏:在没有读取器的情况下写入管道。
默认操作是忽略信号。
- signal.SIGSEGV
- 分段错误:无效的内存引用。
- signal.SIGTERM
- 终止信号。
- signal.SIGUSR1
- 用户自定义信号 1.
- signal.SIGUSR2
- 用户自定义信号 2.
- signal.SIGWINCH
- 窗口调整大小信号。
- SIG*
- 所有信号编号均以符号方式定义。 例如,挂断信号定义为signal.SIGHUP; 变量名称与 C 程序中使用的名称相同,如
<signal.h>
中所示。 'signal()
' 的 Unix 手册页列出了现有信号(在某些系统上,这是 signal(2),在其他系统上,该列表在 signal(7) )。 请注意,并非所有系统都定义相同的一组信号名称; 只有系统定义的那些名称才由该模块定义。
- signal.CTRL_C_EVENT
Ctrl+C击键事件对应的信号。 此信号只能与 os.kill() 一起使用。
3.2 版中的新功能。
- signal.CTRL_BREAK_EVENT
Ctrl+Break击键事件对应的信号。 此信号只能与 os.kill() 一起使用。
3.2 版中的新功能。
- signal.NSIG
- 比最高信号数多一的数。
- signal.ITIMER_REAL
- 实时递减间隔定时器,并在到期时下发SIGALRM。
- signal.ITIMER_VIRTUAL
- 仅在进程执行时递减间隔计时器,并在到期时传递 SIGVTALRM。
- signal.ITIMER_PROF
- 当进程执行时和系统代表进程执行时,都会减少间隔计时器。 与 ITIMER_VIRTUAL 结合使用,该计时器通常用于分析应用程序在用户和内核空间中花费的时间。 SIGPROF 在到期时交付。
- signal.SIG_BLOCK
how 参数到 pthread_sigmask() 的可能值,指示要阻止信号。
3.3 版中的新功能。
- signal.SIG_UNBLOCK
how 参数的可能值 pthread_sigmask() 指示信号将被解除阻塞。
3.3 版中的新功能。
- signal.SIG_SETMASK
pthread_sigmask() 的 how 参数的可能值指示要替换信号掩码。
3.3 版中的新功能。
signal 模块定义了一个例外:
- exception signal.ItimerError
引发来自底层 setitimer() 或 getitimer() 实现的错误信号。 如果将无效的间隔计时器或负时间传递给 setitimer(),则会出现此错误。 此错误是 OSError 的子类型。
signal 模块定义了以下函数:
- signal.alarm(time)
- 如果 time 非零,该函数请求在 time 秒内向进程发送 SIGALRM 信号。 任何先前安排的闹钟都会被取消(任何时候只能安排一个闹钟)。 返回的值是任何先前设置的警报被传递之前的秒数。 如果时间为零,则不安排闹钟,取消任何安排的闹钟。 如果返回值为零,则当前没有调度警报。
- signal.getsignal(signalnum)
- 返回信号 signalnum 的当前信号处理程序。 返回值可能是一个可调用的 Python 对象,也可能是特殊值 signal.SIG_IGN、signal.SIG_DFL 或 None 之一。 这里,signal.SIG_IGN 表示之前忽略了该信号,signal.SIG_DFL 表示之前使用了默认处理信号的方式,
None
表示之前的信号处理程序不是从 Python 安装的。
- signal.strsignal(signalnum)
返回信号signalnum的系统描述,如“中断”、“分段故障”等。 如果信号未被识别,则返回 None。
3.8 版中的新功能。
- signal.valid_signals()
返回此平台上的一组有效信号编号。 如果系统保留一些信号供内部使用,则该值可能小于
range(1, NSIG)
。3.8 版中的新功能。
- signal.pause()
使进程休眠,直到收到信号; 然后将调用适当的处理程序。 什么都不返回。
- signal.raise_signal(signum)
向调用进程发送信号。 什么都不返回。
3.8 版中的新功能。
- signal.pidfd_send_signal(pidfd, sig, siginfo=None, flags=0)
将信号 sig 发送到文件描述符 pidfd 引用的进程。 Python 目前不支持 [X38X]siginfo 参数; 它必须是
None
。 flags 参数是为将来的扩展提供的; 当前没有定义标志值。有关更多信息,请参阅 pidfd_send_signal(2) 手册页。
3.9 版中的新功能。
- signal.pthread_kill(thread_id, signalnum)
将信号 signalnum 发送到线程 thread_id,这是与调用者在同一进程中的另一个线程。 目标线程可以执行任何代码(Python 与否)。 但是,如果目标线程正在执行 Python 解释器,则 Python 信号处理程序将由主解释器 的主线程 执行。 因此,向特定 Python 线程发送信号的唯一目的是强制正在运行的系统调用失败并显示 InterruptedError。
使用 threading.get_ident() 或 threading.Thread 对象的 ident 属性为 thread_id 获取合适的值。
如果signalnum为0,则不发送信号,但仍进行错误检查; 这可用于检查目标线程是否仍在运行。
另见 os.kill()。
3.3 版中的新功能。
- signal.pthread_sigmask(how, mask)
获取和/或更改调用线程的信号掩码。 信号掩码是一组信号,其传递当前被调用者阻止。 将旧信号掩码作为一组信号返回。
调用的行为取决于 how 的值,如下所示。
SIG_BLOCK:阻塞信号的集合是当前集合和 mask 参数的并集。
SIG_UNBLOCK:mask 中的信号从当前的阻塞信号集中移除。 允许尝试解锁未被阻塞的信号。
SIG_SETMASK:阻塞信号集设置为 mask 参数。
mask 是一组信号编号(例如 {signal.SIGINT, signal.SIGTERM})。 将 valid_signals() 用于包含所有信号的完整掩码。
例如,
signal.pthread_sigmask(signal.SIG_BLOCK, [])
读取调用线程的信号掩码。SIGKILL 和
SIGSTOP
不能被屏蔽。另见 pause()、sigpending() 和 sigwait()。
3.3 版中的新功能。
- signal.setitimer(which, seconds, interval=0.0)
设置由 指定的给定间隔计时器(signal.ITIMER_REAL、signal.ITIMER_VIRTUAL 或 signal.ITIMER_PROF 之一)在 之后触发 X168X]seconds(接受浮点数,不同于 alarm()),之后每隔 interval 秒(如果 interval 非零)。 由指定的间隔定时器,可以通过将秒设置为零来清除。
当间隔计时器触发时,会向进程发送信号。 发送的信号取决于所使用的定时器; signal.ITIMER_REAL 将发送 SIGALRM,signal.ITIMER_VIRTUAL 发送
SIGVTALRM
,而 signal.ITIMER_7PROX 将发送 ]。旧值作为元组返回:(延迟,间隔)。
尝试传递无效的间隔计时器将导致 ItimerError。
- signal.getitimer(which)
- 返回由 which 指定的给定间隔计时器的当前值。
- signal.set_wakeup_fd(fd, *, warn_on_full_buffer=True)
将唤醒文件描述符设置为 fd。 当接收到信号时,信号编号作为单个字节写入 fd。 库可以使用它来唤醒轮询或选择调用,从而允许完全处理信号。
返回旧的唤醒 fd(如果未启用文件描述符唤醒,则返回 -1)。 如果 fd 为 -1,则禁用文件描述符唤醒。 如果不是 -1,则 fd 必须是非阻塞的。 在再次调用 poll 或 select 之前,库需要从 fd 中删除任何字节。
当线程开启时,该函数只能从主解释器的主线程调用; 尝试从其他线程调用它会导致引发 ValueError 异常。
有两种常用方法可以使用此功能。 在这两种方法中,您都使用 fd 在信号到达时唤醒,但是它们在确定 哪个 信号已到达的方式上有所不同。
在第一种方法中,我们从 fd 的缓冲区中读取数据,字节值为您提供信号编号。 这很简单,但在极少数情况下会遇到问题:通常 fd 的缓冲区空间量有限,如果太多信号到达太快,则缓冲区可能已满,并且可能会丢失一些信号。 如果你使用这种方法,那么你应该设置
warn_on_full_buffer=True
,这至少会导致在信号丢失时向stderr打印警告。在第二种方法中,我们使用唤醒 fd only 进行唤醒,并忽略实际字节值。 在这种情况下,我们只关心 fd 的缓冲区是空的还是非空的; 一个完整的缓冲区根本不表示有问题。 如果您使用这种方法,那么您应该设置
warn_on_full_buffer=False
,这样您的用户就不会被虚假的警告消息所迷惑。3.5 版更改: 在 Windows 上,该函数现在也支持套接字句柄。
3.7 版更改: 添加
warn_on_full_buffer
参数。
- signal.siginterrupt(signalnum, flag)
改变系统调用重启行为:如果flag为False,系统调用会在被信号signalnum中断时重启,否则系统调用会被中断。 什么都不返回。
请注意,使用 signal() 安装信号处理程序将通过隐式调用
siginterrupt()
与给定信号的真实 标志 值将重启行为重置为可中断。
- signal.signal(signalnum, handler)
将信号 signalnum 的处理程序设置为函数 handler。 handler 可以是带有两个参数的可调用 Python 对象(见下文),或特殊值 signal.SIG_IGN 或 signal.SIG_DFL 之一。 将返回之前的信号处理程序(参见上面 getsignal() 的描述)。 (有关更多信息,请参阅 Unix 手册页 signal(2)。)
当线程开启时,该函数只能从主解释器的主线程调用; 尝试从其他线程调用它会导致引发 ValueError 异常。
调用 handler 有两个参数:信号编号和当前堆栈帧(
None
或帧对象;有关帧对象的说明,请参阅类型中的 说明hierarchy 或查看 inspect 模块中的属性描述)。在 Windows 上,signal() 只能用 SIGABRT、SIGFPE、SIGILL、SIGINT、[ X123X]SIGSEGV、SIGTERM 或 SIGBREAK。 在任何其他情况下都会引发 ValueError。 请注意,并非所有系统都定义相同的一组信号名称; 如果信号名称未定义为
SIG*
模块级常量,则会引发 AttributeError。
- signal.sigpending()
检查等待传递给调用线程的信号集(即,在阻塞时已引发的信号)。 返回待处理信号的集合。
另见 pause()、pthread_sigmask() 和 sigwait()。
3.3 版中的新功能。
- signal.sigwait(sigset)
暂停调用线程的执行,直到信号集 sigset 中指定的信号之一被传递。 该函数接受信号(将其从待处理的信号列表中删除),并返回信号编号。
另见 pause()、pthread_sigmask()、sigpending()、sigwaitinfo() 和 sigtimedwait() ]。
3.3 版中的新功能。
- signal.sigwaitinfo(sigset)
暂停调用线程的执行,直到信号集 sigset 中指定的信号之一被传递。 该函数接受信号并将其从待处理的信号列表中删除。 如果 sigset 中的信号之一已经等待调用线程,该函数将立即返回有关该信号的信息。 不会为传递的信号调用信号处理程序。 如果该函数被不在 sigset 中的信号中断,则会引发 InterruptedError。
返回值是一个代表
siginfo_t
结构体中包含的数据的对象,即:si_signo
、si_code
、si_errno
、si_pid
、si_uid
、si_status
、si_band
。另见 pause()、sigwait() 和 sigtimedwait()。
3.3 版中的新功能。
3.5 版更改: 现在,如果被不在 sigset 中的信号中断,该函数将重试,并且信号处理程序不会引发异常(请参阅 PEP 475 的基本原理)。
- signal.sigtimedwait(sigset, timeout)
类似于 sigwaitinfo(),但需要一个额外的 timeout 参数来指定超时。 如果 timeout 指定为
0
,则执行轮询。 如果发生超时,则返回 None。另见 pause()、sigwait() 和 sigwaitinfo()。
3.3 版中的新功能。
在 3.5 版中更改:如果被不在 sigset 中的信号中断并且信号处理程序不会引发异常(参见 PEP 475 了解基本原理)。
例子
这是一个最小的示例程序。 它使用 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
关于 SIGPIPE 的注意事项
当标准输出的接收器提前关闭时,将程序的输出管道输出到 head(1) 等工具将导致 SIGPIPE 信号发送到您的进程。 这会导致像 BrokenPipeError: [Errno 32] Broken pipe
这样的异常。 要处理这种情况,请按如下方式包装您的入口点以捕获此异常:
import os
import sys
def main():
try:
# simulate large output (your code replaces this loop)
for x in range(10000):
print("y")
# flush output here to force SIGPIPE to be triggered
# while inside this try block.
sys.stdout.flush()
except BrokenPipeError:
# Python flushes standard streams on exit; redirect remaining output
# to devnull to avoid another BrokenPipeError at shutdown
devnull = os.open(os.devnull, os.O_WRONLY)
os.dup2(devnull, sys.stdout.fileno())
sys.exit(1) # Python exits with error code 1 on EPIPE
if __name__ == '__main__':
main()
不要将 SIGPIPE 的处置设置为 SIG_DFL 以避免 BrokenPipeError。 这样做会导致您的程序意外退出,只要您的程序仍在写入时任何套接字连接被中断。