# 18.8. `signal` — 设置异步事件处理程序

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

## 18.8.1. 一般规则

The `signal.signal()` function allows defining custom handlers to be executed when a signal is received. A small number of default handlers are installed: `SIGPIPE` is ignored (so write errors on pipes and sockets can be reported as ordinary Python exceptions) and `SIGINT` is translated into a `KeyboardInterrupt` exception.

一旦设置，特定信号的处理程序将保持安装，直到它被显式重置（ Python 模拟 BSD 样式接口而不管底层实现），但 `SIGCHLD` 的处理程序除外，它遵循底层实现。

### 18.8.1.1. 执行 Python 信号处理程序

Python 信号处理程序不会在低级（ C ）信号处理程序中执行。相反，低级信号处理程序设置一个标志，告诉 virtual machine 稍后执行相应的 Python 信号处理程序（例如在下一个 bytecode 指令）。这会导致：

- 捕获同步错误是没有意义的，例如 `SIGFPE` 或 `SIGSEGV` ，它们是由 C 代码中的无效操作引起的。Python 将从信号处理程序返回到 C 代码，这可能会再次引发相同的信号，导致 Python 显然的挂起。 从Python 3.3开始，你可以使用 `faulthandler` 模块来报告同步错误。

- 纯 C 中实现的长时间运行的计算（例如在大量文本上的正则表达式匹配）可以在任意时间内不间断地运行，而不管接收到任何信号。计算完成后将调用 Python 信号处理程序。

### 18.8.1.2. 信号与线程

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

此外，只允许主线程设置新的信号处理程序。

## 18.8.2. 模块内容

3.5 版更變: 信号（ SIG* ），处理程序（ `SIG_DFL` ， `SIG_IGN`）和 sigmask（ `SIG_BLOCK` ， `SIG_UNBLOCK` ， `SIG_SETMASK` ）下面列出的相关常量变成了 `enums` 。 `getsignal()` ， `pthread_sigmask()` ， `sigpending()` 和 `sigwait()` 函数返回人类可读的 `enums` 。

在 `signal` 模块中定义的变量是：

`signal.``SIG_DFL`

  这是两种标准信号处理选项之一；它只会执行信号的默认函数。 例如，在大多数系统上，对于 `SIGQUIT` 的默认操作是转储核心并退出，而对于 `SIGCHLD` 的默认操作是简单地忽略它。

`signal.``SIG_IGN`

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

`SIG*`

  All the signal numbers are defined symbolically. For example, the hangup signal is defined as `signal.SIGHUP`; the variable names are identical to the names used in C programs, as found in `<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)). Note that not all systems define the same set of signal names; only those names defined by the system are defined by this module.

`signal.``CTRL_C_EVENT`

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

  Availability: Windows.

  3.2 版新加入.

`signal.``CTRL_BREAK_EVENT`

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

  Availability: Windows.

  3.2 版新加入.

`signal.``NSIG`

  比最高信号数多一。

`signal.``ITIMER_REAL`

  实时递减间隔计时器，并在到期时发送 `SIGALRM` 。

`signal.``ITIMER_VIRTUAL`

  仅在进程执行时递减间隔计时器，并在到期时发送 SIGVTALRM 。

`signal.``ITIMER_PROF`

  当进程执行时以及当系统替进程执行时都会减小间隔计时器。 这个计时器与 ITIMER_VIRTUAL 相配结，通常被用于分析应用程序在用户和内核空间中花费的时间。 SIGPROF 会在超期时被发送。

`signal.``SIG_BLOCK`

  `pthread_sigmask()` 的 how 形参的一个可能的值，表明信号将会被阻塞。

  3.3 版新加入.

`signal.``SIG_UNBLOCK`

  `pthread_sigmask()` 的 how 形参的是个可能的值，表明信号将被解除阻塞。

  3.3 版新加入.

`signal.``SIG_SETMASK`

  `pthread_sigmask()` 的 how 形参的一个可能的值，表明信号掩码将要被替换。

  3.3 版新加入.

`signal` 模块定义了一个异常:

exception `signal.``ItimerError`

  作为来自下层 `setitimer()` 或 `getitimer()` 实现错误的信号被引发。 如果将无效的定时器或负的时间值传给 `setitimer()` 就导致这个错误。 此错误是 `OSError` 的子类型。

  3.3 版新加入: 此错误是 `IOError` 的子类型，现在则是 `OSError` 的别名。

`signal` 模块定义了以下函数:

`signal.``alarm`(time)

  If time is non-zero, this function requests that a `SIGALRM` signal be sent to the process in time seconds. Any previously scheduled alarm is canceled (only one alarm can be scheduled at any time). The returned value is then the number of seconds before any previously set alarm was to have been delivered. If time is zero, no alarm is scheduled, and any scheduled alarm is canceled. If the return value is zero, no alarm is currently scheduled. (See the Unix man page alarm(2).) Availability: Unix.

`signal.``getsignal`(signalnum)

  返回当前用于信号 signalnum 的信号处理程序。 返回值可以是一个 Python 可调用对象，或是特殊值 `signal.SIG_IGN`, `signal.SIG_DFL` 或 `None` 之一。 在这里，`signal.SIG_IGN` 表示信号在之前被忽略，`signal.SIG_DFL` 表示之前在使用默认的信号处理方式，而 `None` 表示之前的信号处理程序未由 Python 安装。

`signal.``pause`()

  Cause the process to sleep until a signal is received; the appropriate handler will then be called. Returns nothing. Not on Windows. (See the Unix man page signal(2).)

  另请参阅 `sigwait()`, `sigwaitinfo()`, `sigtimedwait()` 和 `sigpending()`。

`signal.``pthread_kill`(thread_id, signalnum)

  Send the signal signalnum to the thread thread_id, another thread in the same process as the caller. The target thread can be executing any code (Python or not). However, if the target thread is executing the Python interpreter, the Python signal handlers will be executed by the main thread. Therefore, the only point of sending a signal to a particular Python thread would be to force a running system call to fail with `InterruptedError`.

  使用 `threading.get_ident()` 或 `threading.Thread` 对象的 `ident` 属性为 thread_id 获取合适的值。

  如果 signalnum 为 0，则不会发送信号，但仍然会执行错误检测；这可被用来检测目标线程是否仍在运行。

  Availability: Unix (see the man page pthread_kill(3) for further information).

  另请参阅 `os.kill()`。

  3.3 版新加入.

`signal.``pthread_sigmask`(how, mask)

  获取和/或修改调用方线程的信号掩码。 信号掩码是一组传送过程目前为调用者而阻塞的信号集。 返回旧的信号掩码作为一组信号。

  该调用的行为取决于 how 的值，具体见下。

  - `SIG_BLOCK`: 被阻塞的信号集是当前集与 mask 参数的并集。

  - `SIG_UNBLOCK`: mask 中的信号会从当前已阻塞信号集中被移除。 允许尝试取消对一个非阻塞信号的阻塞。

  - `SIG_SETMASK`: 已阻塞信号集会被设为 mask 参数的值。

  mask is a set of signal numbers (e.g. {`signal.SIGINT`, `signal.SIGTERM`}). Use `range(1, signal.NSIG)` for a full mask including all signals.

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

  Availability: Unix. See the man page sigprocmask(3) and pthread_sigmask(3) for further information.

  另请参阅 `pause()`, `sigpending()` 和 `sigwait()`。

  3.3 版新加入.

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

  Sets given interval timer (one of `signal.ITIMER_REAL`, `signal.ITIMER_VIRTUAL` or `signal.ITIMER_PROF`) specified by which to fire after seconds (float is accepted, different from `alarm()`) and after that every interval seconds. The interval timer specified by which can be cleared by setting seconds to zero.

  当一个间隔计时器启动时，会有信号发送至进程。 所发送的具体信号取决于所使用的计时器；`signal.ITIMER_REAL` 将发送 `SIGALRM`, `signal.ITIMER_VIRTUAL` 将发送 `SIGVTALRM`, 而 `signal.ITIMER_PROF` 将发送 `SIGPROF`.

  原有的值会以元组: (delay, interval) 的形式被返回。

  Attempting to pass an invalid interval timer will cause an `ItimerError`. Availability: Unix.

`signal.``getitimer`(which)

  Returns current value of a given interval timer specified by which. Availability: Unix.

`signal.``set_wakeup_fd`(fd)

  将唤醒文件描述符设为 fd。 当接收到信号时，会将信号编号以单个字节的形式写入 fd。 这可被其它库用来唤醒一次 poll 或 select 调用，以允许该信号被完全地处理。

  原有的唤醒 fd 会被返回（或者如果未启用文件描述符唤醒则返回 -1）。 如果 fd 为 -1，文件描述符唤醒会被禁用。 如果不为 -1，则 fd 必须为非阻塞型。 需要由库来负责在重新调用 poll 或 select 之前从 fd 移除任何字节数据。

  Use for example `struct.unpack('%uB' % len(data), data)` to decode the signal numbers list.

  When threads are enabled, this function can only be called from the main thread; attempting to call it from other threads will cause a `ValueError` exception to be raised.

  3.5 版更變: 在 Windows 上，此函数现在也支持套接字处理。

`signal.``siginterrupt`(signalnum, flag)

  Change system call restart behaviour: if flag is `False`, system calls will be restarted when interrupted by signal signalnum, otherwise system calls will be interrupted. Returns nothing. Availability: Unix (see the man page siginterrupt(3) for further information).

  请注意用 `signal()` 安装信号处理程序将重启行为重置为可通过显式调用 `siginterrupt()` 并为给定信号的 flag 设置真值来实现中断。

`signal.``signal`(signalnum, handler)

  Set the handler for signal signalnum to the function handler. handler can be a callable Python object taking two arguments (see below), or one of the special values `signal.SIG_IGN` or `signal.SIG_DFL`. The previous signal handler will be returned (see the description of `getsignal()` above). (See the Unix man page signal(2).)

  When threads are enabled, this function can only be called from the main thread; attempting to call it from other threads will cause a `ValueError` exception to be raised.

  handler 将附带两个参数调用：信号编号和当前堆栈帧 (`None` 或一个帧对象；有关帧对象的描述请参阅 类型层级结构描述 或者参阅 `inspect` 模块中的属性描述)。

  在 Windows 上，`signal()` 调用只能附带 `SIGABRT`, `SIGFPE`, `SIGILL`, `SIGINT`, `SIGSEGV`, `SIGTERM` 或 `SIGBREAK`。 任何其他值都将引发 `ValueError`。 请注意不是所有系统都定义了同样的信号名称集合；如果一个信号名称未被定义为 `SIG*` 模块层级常量则将引发 `AttributeError`。

`signal.``sigpending`()

  检查正在等待传送给调用方线程的信号集合（即在阻塞期间被引发的信号）。 返回正在等待的信号集合。

  Availability: Unix (see the man page sigpending(2) for further information).

  另请参阅 `pause()`, `pthread_sigmask()` 和 `sigwait()`。

  3.3 版新加入.

`signal.``sigwait`(sigset)

  挂起调用方线程的执行直到信号集合 sigset 中指定的信号之一被传送。 此函数会接受该信号（将其从等待信号列表中移除），并返回信号编号。

  Availability: Unix (see the man page sigwait(3) for further information).

  另请参阅 `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`。

  Availability: Unix (see the man page sigwaitinfo(2) for further information).

  另请参阅 `pause()`, `sigwait()` 和 `sigtimedwait()`。

  3.3 版新加入.

  3.5 版更變: 当被某个 不在 sigset 中的信号中断时本函数将结束并且信号处理程序不会引发异常 (其理由参见 PEP 475)。

`signal.``sigtimedwait`(sigset, timeout)

  类似于 `sigwaitinfo()`，但会接受一个额外的 timeout 参数来指定超时限制。 如果将 timeout 指定为 `0`，则会执行轮询。 如果发生超时则返回 `None`。

  Availability: Unix (see the man page sigtimedwait(2) for further information).

  另请参阅 `pause()`, `sigwait()` 和 `sigwaitinfo()`。

  3.3 版新加入.

  3.5 版更變: 现在当此函数被某个不在 sigset 中的信号中断时将以计算出的 timeout 进行重试并且信号处理程序不会引发异常（请参阅 PEP 475 了解其理由）。

## 18.8.3. 示例

这是一个最小示例程序。 它使用 `alarm()` 函数来限制等待打开一个文件所花费的时间；这在文件为无法开启的串行设备时会很有用处，此情况通常会导致 `os.open()` 无限期地挂起。 解决办法是在打开文件之前设置 5 秒钟的 alarm；如果操作耗时过长，将会发送 alarm 信号，并且处理程序会引发一个异常。

    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