signal.Notify本身不拦截信号,只镜像转发;必须在调用前执行signal.Ignore(os.Interrupt, syscall.SIGTERM)屏蔽默认终止行为,否则程序收到SIGINT/SIGTERM仍会立即退出,无法执行清理逻辑。

os/signal.Notify 本身不拦截信号,只镜像转发;不配 signal.Ignore 或默认行为处理,程序收到 SIGINT/SIGTERM 会直接退出,根本走不到你的清理逻辑。
为什么 signal.Notify 后程序还是秒退?
常见错误是只调用 signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM),但没屏蔽系统默认终止行为。Go 进程对这两个信号的默认响应就是立即退出,不管 channel 是否已注册。
必须在 Notify 前加一句:signal.Ignore(os.Interrupt, syscall.SIGTERM),否则信号一来就崩, 根本没机会执行。
- Windows 下
syscall.SIGTERM不可用,要用os.Interrupt兼容 -
signal.Ignore是全局生效的,不影响其他信号(如SIGQUIT) - 别用
signal.Stop替代 —— 它只停转发,不改默认行为,照样秒退
如何让 HTTP server、worker、DB 连接统一退出?
靠多个 if 手动判断太脆弱。推荐用 <code>context.WithCancel + sync.WaitGroup 统一驱动:
- 启动时:创建
ctx, cancel := context.WithCancel(context.Background()) - 每个长期 goroutine(HTTP server、后台任务、连接池)都接收该
ctx,并在select中监听ctx.Done() - 收到信号后只调一次
cancel(),所有依赖该 ctx 的组件自动感知 - 用
WaitGroup等它们各自 clean up 完再退出,避免提前收尾
注意:http.Server.Shutdown 必须传带超时的 ctx(如 context.WithTimeout(ctx, 10*time.Second)),不能用 context.Background() —— 否则可能卡死或丢请求。
立即学习“go语言免费学习笔记(深入)”;
Shutdown 返回了 error 就代表失败?
不是。srv.Shutdown 是异步发起关闭流程,返回非 nil error 通常只说明“上下文超时”或“被取消”,不代表资源没释放。关键看它是否已开始执行:
- 只要没 panic 或 panic recover 捕获到
http.ErrServerClosed,就说明 shutdown 已触发 - 真正要检查的是:连接是否还在 accept 新请求(
srv.Close会立刻断开,Shutdown不会) - 日志里出现
"HTTP server stopped"且端口不再监听,才是成功标志
别在 Shutdown 后加 time.Sleep 等待 —— 它不阻塞,sleep 只是凭运气碰巧等完。
测试优雅退出时最容易忽略什么?
手动 kill -2 或 Ctrl+C 只能验证单次路径。真实场景下容易出问题的是:
- 并发发多次信号(比如容器重启时连续发
SIGTERM+SIGKILL),没做信号去重会导致重复 cancel 或 panic - worker goroutine 在
ctx.Done()后仍尝试写入未关闭的 channel,引发 panic - DB 连接池关闭早于 worker 停止,导致 worker 执行 cleanup 时 DB 已不可用
可靠做法:用压测工具持续发请求,同时发信号,观察日志里是否有 connection refused、use of closed network connection,以及端口是否真释放。


















