Python信号处理机制
本篇的信号处理机制不是指Python的signal
模块的使用,而是指Python解释器本身如何处理信号以及如何实现signal
模块。Python解释器处理信号机制需要做好两件事情:
- Python解释器与操作系统有关信号的交互
- Python解释器实现信号语义的API接口和模块
大体上,Python解释器对信号的实现总体思路比较简单。Python解释器对信号做一层封装,在这层封装中处理信号,以及信号发生时的回调函数,使之能够纳入整个Python虚拟机的运行中。我们先从信号的初始化开始一点点揭露整个运作机制。
信号机制的初始化
信号机制的初始化是在Python初始化整个解释器时开始的,Python在初始化函数中调用initsigs
来进行整个系统以及singal
模块的初始化。
|
|
直接进入到singalmodule.c
中看signal
模块以及信号的初始化。
|
|
可以看到Python将用户自定义信号处理函数保存在Handler
数组中,而实际上向系统注册signal_handler
函数。这个signal_handler
函数成为信号发生时沟通Python解释器和用户自定义信号处理函数的桥梁。可以从signal.signal
的实现中清楚的看到这一点。
|
|
信号产生时Python的动作
当信号产生时,操作系统会调用Python解释器注册的信号处理函数,即上文中的signal_handler
函数。这个函数将对应的Handler
结构中的信号产生标志tripped
设置为1,然后将一个统一信号处理函数trip_signal
作为pending_call
注册到Python虚拟机的执行栈中。于是,Python在虚拟机执行过程中调用pending_call
并执行各个用户自定义的信号处理函数。
|
|
这里面的PyErr_CheckSignals
函数也会被其他模块调用直接信号的处理。例如,在file.read
读取文件过程中中断,Python对调用该函数进行信号处理。至此,可以看到整个信号处理的流程:
- 初始化signal模块,将对应的操作系统信号值、函数转化成Python对象
- 用户设置信号就向操作系统注册函数
signal_handler
,并将用户自定义信号处理函数设置到对应的Handler
数组中 - 当信号发生时,操作系统调用
signal_handler
设置tripped=1
,然后调用trip_signal
将统一处理函数checksignals_witharg
作为pendingcall
注册到Python虚拟机的执行栈中。 - Python虚拟机在处理
pendingcall
时调用checksignals_withargs
,从而信号处理函数得以执行。 - 另外,Python其他模块可以直接调用
PyErr_CheckSignals
进行信号处理。
Python信号的语义
通过注释以及代码剖析可以归纳Python的信号语义:
- 只有主线程能够设置、捕获和处理信号
- 信号设置一直有效(
signal_handler
中会再次注册信号处理函数) - 多次信号,可能会被合并处理一次
- 按照信号值从小到大处理
信号实例
主线程才能捕获信号
|
|
|
|
- [1] Python中的线程都是分离的,因此主线程很快退出。信号不能发送到主线程,因此不能被执行。
信号可能只被处理一次
|
|
|
|
- [1] 主线程的
t.join
一直阻塞,因此在子线程没有退出前不能处理信号。(C语言的信号处理是可以打断堵塞信号的) - [2] 信号在有机会处理之前发生了两次信号,但是只处理了一次。
Python信号的特殊性
Python的信号语义与Linux的C语言的信号语义有一些不同。
- Python信号的处理函数会一直有效;而Linux除非特殊设置否则信号处理函数默认只调用一次就被恢复
- Python信号只能在主线程中设置、捕获和处理
- Python信号不能打断堵塞操作(因为信号发生时子线程在运行)
(完)