Python signal模块可捕获SIGINT、SIGHUP、SIGTERM、SIGUSR1/2等可处理信号,但无法捕获SIGKILL和SIGSTOP;handler内应仅设标志位,由主循环检测退出并执行清理。

Python 的 signal 模块能捕获退出信号,但不能可靠拦截 SIGKILL(kill -9)和 SIGSTOP;真正能“优雅退出”的关键,是用 signal.signal() 注册清理逻辑,并确保主流程不阻塞在不可中断的系统调用上。
哪些信号能被 Python 捕获和处理?
Python 只能响应可被捕获的 POSIX 信号。最常用的是:
-
SIGINT(Ctrl+C):默认引发KeyboardInterrupt,可重载 -
SIGHUP(终端挂起/守护进程重载):常用于触发配置重载 -
SIGTERM(kill默认发送):标准退出请求,应优先响应 -
SIGUSR1/SIGUSR2(Linux/macOS):自定义用途,比如触发日志滚动或 debug dump
SIGKILL 和 SIGSTOP 由内核强制执行,任何用户态注册的 handler 都不会被调用——这点常被忽略,误以为“加了 signal 就万无一失”。
为什么 handler 里不能做耗时操作?
信号处理函数运行在异步上下文,且 Python 的 GIL 在 handler 进入时被持有,但:
- 不能调用大多数 C 扩展(如
requests.get()、time.sleep()、print()等 I/O 函数)——它们不是 async-signal-safe - 不能安全地操作线程、锁、或大部分内置容器(如
list.append()在多线程下可能崩溃) - 推荐只做极轻量的事:设置一个全局 flag(
volatile_flag = True),或写入os.write()到已打开的 fd
例如,下面这段代码是危险的:
def handler(signum, frame):
logging.info("Shutting down...") # ❌ logging 不是 async-signal-safe
time.sleep(1) # ❌ time.sleep 不可重入
sys.exit(0)
正确做法是设 flag,让主循环主动检查并退出。
如何让主程序响应信号后真正“优雅”退出?
核心思路:信号 handler 只负责“通知”,退出逻辑交给主流程。常见模式如下:
- 用
threading.Event或全局布尔变量(如should_exit = False)作为退出信号 - 主循环中定期检查该 flag(尤其在
time.sleep()前后) - 若使用
socket.accept()、queue.get()等阻塞调用,需配合超时(如sock.settimeout(1))或使用select.select(),否则信号可能延迟送达 - 清理资源(关闭文件、断开数据库连接、提交未完成任务)必须放在主流程中,而非 handler 内
示例片段:
import signal
import time
<p>should_exit = False</p><p>def signal_handler(signum, frame):
global should_exit
should_exit = True</p><p>signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGINT, signal_handler)</p><p>while not should_exit:
try:</p><h1>模拟工作</h1><pre class="brush:php;toolbar:false;"> time.sleep(0.5)
except KeyboardInterrupt:
should_exit = True✅ 安全执行清理
print("Cleanup: closing connections...") time.sleep(0.2) print("Exited gracefully.")
多线程环境下 signal 有什么限制?
Unix-like 系统中,信号只递送给**主线程**,子线程无法独立注册 handler。这意味着:
- 所有
signal.signal()调用必须在主线程中完成,且仅对主线程有效 - 如果主线程早早退出(比如只负责启动 worker),信号将无法被处理——常见于用
threading.Thread启动后台任务却没留主线程守着的场景 - 若用
multiprocessing,每个子进程需单独注册 handler(fork 后信号注册不继承)
所以,别指望在某个 worker 线程里调用 signal.signal() ——它会被忽略,且不报错。
真正难的不是注册 handler,而是确保主流程始终可响应、清理逻辑不遗漏、以及理解哪些信号根本拦不住。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











