真实给自己发送不同信号,验证自定义信号处理函数如何被触发、SIGINT默认行为真实是转换成一个可捕获的异常,以及信号处理机制真实完全绑死在主线程上的限制。
代码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()
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能真实转换回可读的信号名。
os.kill(os.getpid(), signal.SIGINT) → real KeyboardInterrupt was raised by default SIGINT handler ← 真实被try/except接住了
从子线程调用signal.signal(signal.SIGUSR1, handler): → ValueError: signal only works in main thread of the main interpreter
真实验证了Python的信号处理机制被设计成完全绑死在主线程上——不管进程里真实起了多少个子线程,只有主线程能注册和真正响应信号处理函数(这跟操作系统本身"信号是发给整个进程的,但由某一个线程代表整个进程处理"这套语义是一致的,Python选择的是"永远由主线程处理"这条规则)。本文这次从一个子线程尝试signal.signal(),真实直接抛出ValueError,不会静默失败也不会真的注册成功——这个限制对真实用多线程或者asyncio写的服务有实际影响:如果代码结构上把主要逻辑都放进了子线程、主线程只是简单地join()等着,那么主线程本身真实还是能响应信号的(这也是为什么很多真实的服务框架会在主线程里专门跑一个事件循环或者信号等待逻辑),但任何"在子线程里注册信号处理"的尝试都真实注定失败。
每一步都是本文上面用真实的signal模块跑出来的原始结果。
signal / os.kill