Python信号处理程序在多信号场景下的行为及可靠性求证
测试代码
import time import signal global_thing = [] class Foreman: def __init__(self): signal.signal(signal.SIGUSR1, self.handle_sigusr1) def handle_sigusr1(self, sig, frame): global_thing.append("x") i_am_nr = len(global_thing) for i in range(4): print("I am taking my sweet time", i_am_nr, i) time.sleep(1) def run_forever(self): # there is no actual code here, all the action happens in the signal handlers time.sleep(3600) Foreman().run_forever()
运行结果
当第一个信号的处理尚未完成时发送第二个信号,输出如下:
$ python example.py I am taking my sweet time 1 0 I am taking my sweet time 1 1 I am taking my sweet time 1 2 I am taking my sweet time 2 0 I am taking my sweet time 2 1 I am taking my sweet time 2 2 I am taking my sweet time 2 3 I am taking my sweet time 1 3
从结果可见,正在执行的信号处理程序会被新到达的同类型信号中断,后续会恢复执行原处理程序。以下针对两个疑问给出解答:
一、信号处理程序可被中断的可靠依据
CPython的信号处理实现依赖于POSIX标准的信号机制,POSIX明确规定:当进程正在处理某一信号时,同类型信号会被暂时阻塞,但如果信号处理程序调用了可中断的系统调用(比如time.sleep()),该系统调用会被新信号打断,内核会将控制权交还给Python解释器,解释器会先处理新的信号处理程序,之后再恢复执行被打断的原处理程序。
另外,CPython底层通过异步信号安全的钩子实现信号处理:信号到达时,解释器会在安全执行点(如字节码指令间隙)触发处理,但如果当前执行的是阻塞系统调用,系统调用会被信号中断,此时解释器会立即处理新的信号处理函数,完成后再回到原系统调用的断点继续执行。
你观察到的行为本质是:第一个信号处理程序中的time.sleep(1)属于可中断系统调用,第二个SIGUSR1到达时,内核打断了这个sleep,Python转而执行第二个信号处理程序,等第二个处理完成后,再回到第一个处理程序继续执行剩余循环。
二、几乎同时到达的信号处理与底层异常(Linux环境)
信号排队规则:Linux中普通信号(如SIGUSR1这类非实时信号)不支持排队——若多个同类型普通信号几乎同时到达,内核只会保留一个待处理实例,CPython只会处理一次该信号,不会触发多次中断。只有实时信号(SIGRTMIN到SIGRTMAX)支持排队,可处理多个同时到达的同类型信号。
底层异常情况:Linux环境下,只要信号处理程序本身逻辑合规,几乎不会出现解释器底层异常:
- 解释器会确保在安全执行点处理信号,若信号在GC、内存分配等关键操作时到达,会延迟处理直到安全点,不会导致崩溃。
- 若信号处理程序中执行了非异步信号安全的操作(如无锁修改全局状态),可能引发数据不一致,但这属于代码逻辑问题,而非解释器底层异常。
内容的提问来源于stack exchange,提问作者Klaas van Schelven

