os.Exit()是最危险的退出方式,因它跳过所有defer、不等待goroutine结束、直接终止进程,导致资源泄漏;正确做法是监听SIGINT/SIGTERM信号,通过context控制优雅关闭。

Go 程序不能靠 os.Exit() 实现安全退出——它跳过所有 defer、不等待 goroutine 结束、直接终止进程,必然导致资源泄漏。
为什么 os.Exit() 是最危险的“退出”方式
很多人在 signal handler 里写 os.Exit(0),以为干净利落。实际后果是:
-
main函数里的defer全部失效(比如没关数据库连接) - 其他 goroutine 中的
defer根本不会执行(临时文件残留、日志未刷盘) -
http.Server.Shutdown()没机会调用,正在处理的请求被硬中断 - 容器平台(如 Kubernetes)发
SIGTERM后,程序直接消失,连接重置率飙升
signal.Notify 必须监听 SIGINT 和 SIGTERM
只监听 os.Interrupt 或 SIGINT 是线上事故高发点:Docker、K8s、systemd 默认发的是 SIGTERM,不是 Ctrl+C。
- 正确注册:
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) - 别加
SIGKILL(kill -9)——它不可捕获,加了也白加 -
sigCh缓冲区至少为 1:make(chan os.Signal, 1),否则并发信号可能丢失 - 监听必须在
go srv.ListenAndServe()之后、之前,顺序错就收不到信号
http.Server.Shutdown() 必须带超时 context
srv.Close() 是暴力关闭;srv.Shutdown(context.Background()) 等价于“立刻关”,都不算优雅。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 必须用
context.WithTimeout(),例如:ctx, cancel := context.WithTimeout(context.Background(), 25*time.Second) - 超时建议比 K8s 的
terminationGracePeriodSeconds少 5 秒,留出清理余量 -
Shutdown()返回context.DeadlineExceeded是预期行为,不用当错误报警 - 返回非
context.Canceled的 error 才需记录(如网络层异常)
goroutine 和依赖组件必须统一响应退出信号
HTTP server 关了 ≠ 进程能退出。DB 连接池、Redis client、定时任务、后台 worker 都得主动停。
- 所有长期 goroutine 必须基于
ctx.Done()控制生命周期,禁用裸time.Sleep() - DB/Redis 客户端要显式调
.Close(),且建议先设SetConnMaxLifetime(0)加速释放 - 避免在 signal handler 里做耗时操作(如写日志、关 DB),只负责通知,清理放主线程
- 用
sync.WaitGroup或done chan struct{}等待所有工作 goroutine 自行退出,不能靠“等 main 返回”
真正难的不是写 shutdown 逻辑,而是让所有 goroutine 都认同一个 ctx、都守同一套退出契约——漏掉一个,进程就卡住不动。

















