# 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代码中的无效操作导致的类似于`SIGFPE`或`SIGSEGV`的同步错误没有什么意义。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_DFL`，`SIG_IGN`）和sigmask（`SIG_BLOCK` `SIG_UNBLOCK`，`SIG_SETMASK`）相关常数变成`enums`。`getsignal()`, `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_IGN`，`signal.SIG_DFL`或`None`之一。这里，`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.SIGINT`，`signal.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_REAL`，`signal.ITIMER_VIRTUAL`或`signal.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_IGN`或`signal.SIG_DFL`的可调用Python对象。将返回上一个信号处理程序（请参阅上面的`getsignal()`的描述）。（请参见Unix手册页信号（2）。）

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

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

  在Windows上, `signal()` 只能调用 `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_signo`，`si_code`，`si_errno` ，`si_pid`，`si_uid`，`si_status`，`si_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