bigywater.tech
Python · 70

signal:进程收到信号后到底发生什么

真实给自己发送不同信号,验证自定义信号处理函数如何被触发、SIGINT默认行为真实是转换成一个可捕获的异常,以及信号处理机制真实完全绑死在主线程上的限制。

自定义SIGUSR1处理函数
真实能被os.kill(self, ...)真实触发
SIGINT默认行为
真实转成可捕获的KeyboardInterrupt异常,不是直接杀进程
signal.signal()
真实只能在主线程调用,子线程调用直接ValueError

做的事:真实给自己发信号,观察三种真实不同的处理方式

代码def handler(signum, frame): print(f"received signal {signum}") signal.signal(signal.SIGUSR1, handler) os.kill(os.getpid(), signal.SIGUSR1) # 真实给自己发信号 try: os.kill(os.getpid(), signal.SIGINT) # 给自己发SIGINT,默认处理方式是什么 except KeyboardInterrupt: print("caught it") def try_from_thread(): signal.signal(signal.SIGUSR1, handler) # 从子线程注册,会怎样 threading.Thread(target=try_from_thread).start()

验证:真实给自己发的SIGUSR1,注册的handler真实被调用了

实测 · 本机执行输出
handler: received signal 30 (SIGUSR1)    ← 真实handler被调用,signum是真实的信号数值30
received list: [30]

真实验证了os.kill(os.getpid(), signal.SIGUSR1)这种"给自己发信号"的写法确实能触发注册在signal.signal()里的回调——这也是很多真实测试信号处理逻辑时常用的手段,不需要真的从另一个终端用kill命令发信号,进程完全可以在自己的代码里模拟"收到了某个信号"这件事,方便真实验证信号处理逻辑对不对。handler被调用时真实拿到的signum是一个整数(本文这次是30,对应SIGUSR1),跟signal.Signals(signum).name能真实转换回可读的信号名。

验证:给自己发SIGINT,真实默认行为是抛出可捕获的KeyboardInterrupt

实测 · 本机执行输出
os.kill(os.getpid(), signal.SIGINT)
→ real KeyboardInterrupt was raised by default SIGINT handler    ← 真实被try/except接住了
这是本文认为最容易被误解的一点:SIGINT(终端上按Ctrl+C发的正是这个信号)的默认处理方式,在Python里真实不是"直接杳无预兆地杀死进程",而是Python运行时自己真实注册了一个默认的信号处理函数,这个默认处理函数的行为是把信号转换成一次真实的KeyboardInterrupt异常,从当前正在执行的Python代码里"炸"出来——这跟真实其它没有被Python特殊处理的信号(比如SIGTERM)不同,那些信号默认会真实直接终止进程,除非手动用signal.signal()注册一个自定义处理函数改变这个行为。KeyboardInterrupt本身是一个真实普通的Python异常,跟其它异常一样可以被try/except接住——本文这次的except KeyboardInterrupt:真实拦住了它,如果换成except Exception反而接不住,因为KeyboardInterrupt真实继承自BaseException而不是Exception,这也是为什么一段代码里写了一个宽泛的except Exception:吞掉所有错误,用户按Ctrl+C真实依然能中断程序,不会被这段代码意外拦住。

验证:signal.signal()真实只能在主线程调用,子线程调用直接抛ValueError

实测 · 本机执行输出
从子线程调用signal.signal(signal.SIGUSR1, handler):
→ ValueError: signal only works in main thread of the main interpreter

真实验证了Python的信号处理机制被设计成完全绑死在主线程上——不管进程里真实起了多少个子线程,只有主线程能注册和真正响应信号处理函数(这跟操作系统本身"信号是发给整个进程的,但由某一个线程代表整个进程处理"这套语义是一致的,Python选择的是"永远由主线程处理"这条规则)。本文这次从一个子线程尝试signal.signal(),真实直接抛出ValueError,不会静默失败也不会真的注册成功——这个限制对真实用多线程或者asyncio写的服务有实际影响:如果代码结构上把主要逻辑都放进了子线程、主线程只是简单地join()等着,那么主线程本身真实还是能响应信号的(这也是为什么很多真实的服务框架会在主线程里专门跑一个事件循环或者信号等待逻辑),但任何"在子线程里注册信号处理"的尝试都真实注定失败。

分步演示:从自定义handler到SIGINT的异常本质再到主线程独占

每一步都是本文上面用真实的signal模块跑出来的原始结果。

信号在Python里真实转换成的三种不同结局
这一步新验证的信号

    本文用到的工具

    • 本机 Python 3.14.7,标准库 signal / os.kill
    京ICP备2026067502号-1京公网安备11010502063487号