signal.Notify 后程序秒退是因为它只负责转发信号到 channel,不阻塞主 goroutine;常见错误是注册后未阻塞等待信号就直接退出。

Go 程序默认不处理任何信号,os/signal 只是注册监听工具,不是自动退出开关——不阻塞主 goroutine、不调用 Shutdown、不传带超时的 context,就谈不上“优雅退出”。
为什么 signal.Notify 后程序秒退?
常见错误是注册完信号就直接执行后续逻辑,甚至 main 函数返回。signal.Notify 只负责把信号发到 channel,它不会暂停程序运行。
- 必须用
显式阻塞主 goroutine(不能用time.Sleep或空for{}) - 缓冲区至少为 1:
make(chan os.Signal, 1),否则快速连发两次SIGTERM会丢一个 - 别把
http.ListenAndServe放在signal.Notify后面——它本身阻塞,信号永远收不到
os.Interrupt 和 syscall.SIGTERM 必须同时监听
本地开发按 Ctrl+C 发的是 os.Interrupt(即 syscall.SIGINT),但线上环境(Kubernetes、systemd、docker stop)默认发的是 syscall.SIGTERM。只监听一个,等于一半场景失效。
- 正确写法:
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) -
os.Interrupt是平台相关常量,Linux/macOS 下等于SIGINT,Windows 下不同,但通常够用 -
syscall.SIGKILL(kill -9)无法捕获,也不该尝试
http.Server.Shutdown 为什么总卡住或提前中断?
Shutdown 本身不等待,它只是通知 server 停止接受新连接,并给已有连接留出完成时间——这个“留出时间”完全依赖你传入的 context。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 绝对不要用
srv.Shutdown(context.Background()):等价于立刻断开所有连接 - 必须用带超时的子 context:
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - 超时时间要略大于你最长请求预期耗时(比如 10–30 秒),太短会粗暴中断,太长则拖慢整体退出
-
Shutdown返回非 nil 错误时,通常是上下文超时,记录日志即可,不必 panic
后台 goroutine 怎么确保不泄漏?
定时器、消息轮询、日志 flush 这类 goroutine 通常阻塞在 select 或 time.Sleep 上,主流程退出时它们还在跑,资源就漏了。
- 所有长期 goroutine 必须响应同一个退出信号源:统一用
context.Context或关闭的done chan - 示例结构:
select { case - 用
sync.WaitGroup计数启动的 goroutine,收到信号后先cancel(),再wg.Wait() - 注意:别在
defer wg.Done()前做未设超时的Shutdown,否则wg.Wait()会卡死
真正容易被忽略的不是怎么写 shutdown 逻辑,而是信号是否真能抵达进程——Docker 默认不转发 SIGTERM,systemd 服务没配 KillMode=process 就会跳过 Go 的处理直接杀掉整个 cgroup。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










