Go 默认对 SIGINT/SIGTERM 会 panic 而非退出,因其将信号视为同步信号直接触发 runtime panic;需用 signal.Ignore 屏蔽默认行为后配合 signal.Notify 捕获,再通过 context.WithCancel 实现优雅退出。

Go 默认对 SIGINT/SIGTERM 会 panic 而非退出
Go 运行时默认把 SIGINT、SIGTERM 当作“同步信号”处理,一收到就直接触发 runtime panic 并退出进程——这不是你写的 panic(),而是 Go 自己的信号处理器干的。它不走 defer,不等 goroutine 结束,更不会调用 srv.Shutdown()。现象是:终端突然打印一堆 stack trace,HTTP 连接被 RST,数据库事务中断,日志截断。这不是优雅退出,是摔门走人。
signal.Notify 必须配合 signal.Ignore 才真正“捕获”
signal.Notify 本身只是把信号“镜像”到 channel,并不拦截系统默认行为。如果你只写:
sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
那程序照样 panic 退出,根本收不到信号。必须在 Notify 前显式屏蔽默认响应:
-
signal.Ignore(syscall.SIGINT, syscall.SIGTERM)—— 最直接,彻底禁用默认 panic 行为 - 或用
signal.Stop+ 手动恢复(更复杂,一般不需要)
漏掉这一步,sigChan 就是个摆设。
立即学习“go语言免费学习笔记(深入)”;
os.Signal channel 类型和缓冲区不能错
常见 panic 错误:panic: signal: unsupported channel type,原因是用了 chan int 或 chan string。必须用 chan os.Signal:
sigChan := make(chan os.Signal, 1)
缓冲区大小至少为 1:否则主 goroutine 还没开始 ,信号就来了,直接丢失。别用 <code>0 容量 channel。
优雅退出的核心是 context.WithCancel + 非阻塞信号处理
收到信号后,不能在 signal handler 里做耗时操作(比如关 DB、写日志),否则会卡住信号接收 loop。正确做法是:
- 用
ctx, cancel := context.WithCancel(context.Background())创建可取消上下文 - 所有长期运行的 goroutine(HTTP server、worker pool、定时任务)都接受该
ctx并监听ctx.Done() - HTTP server 用
srv.Shutdown(ctx),不是srv.Close() - 信号 goroutine 只负责调用
cancel(),然后 return
真正的清理逻辑放在各组件自己的 defer 或 Done() 处理中,而不是堆在 signal handler 里。
最易被忽略的一点:Kubernetes 只发一次 SIGTERM,之后 grace period 到期就发 SIGKILL(不可捕获)。你只有这一次机会把所有 cleanup 串起来——context 传递、超时控制、资源依赖顺序,缺一不可。


















