必须在 http.ListenAndServe 前调用 signal.Notify,否则主 goroutine 阻塞后无法监听信号;channel 缓冲区须设为 1;应监听 os.Interrupt 和 syscall.SIGTERM;收到信号后需先调用 server.Shutdown 等待请求完成,再执行清理。

Go 程序不会自动捕获 SIGINT 或 SIGTERM,不显式调用 signal.Notify 就直接退出——清理逻辑根本没机会执行。
为什么 signal.Notify 必须在 http.ListenAndServe 之前调用
主 goroutine 一旦进入 http.ListenAndServe(或任何阻塞型长期运行逻辑),就再也没法回到主流程去监听信号。如果 signal.Notify 写在它后面,等于没写。
- 信号到达时程序已无监听者,OS 直接终止进程
- 常见错误:把
signal.Notify放进go func() { ... }()但主 goroutine 立即结束,整个程序退出 - 正确顺序:初始化 →
signal.Notify→ 启动 server → 阻塞等待信号
channel 缓冲区设为 1 是硬性要求
无缓冲 channel 在信号到来时可能阻塞(尤其并发发多次 kill -15),导致后续信号被丢弃;缓冲区大于 1 又容易掩盖逻辑问题(比如重复处理)。make(chan os.Signal, 1) 是最稳妥的折中。
- 只监听
os.Interrupt不够:它只是SIGINT的别名,线上kill -15 $PID发的是SIGTERM - 推荐写法:
signal.Notify(sigs, os.Interrupt, syscall.SIGTERM) - 别漏掉
syscall导入——SIGTERM不在os包里
收到信号后不能直接退出,得先 shutdown server
http.Server.Shutdown 不是“立刻关”,而是先关闭 listener,再等活跃请求完成。如果跳过这步直接 os.Exit,正在处理的请求会被粗暴中断。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 必须传入带超时的
context,例如ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) -
server.Shutdown(ctx)返回后,再执行 DB 关闭、日志 flush 等清理 - 别在 signal handler 里直接调
server.Close——它会立即断开连接,不算优雅
主 goroutine 阻塞方式选 select{} 还是
用 最简单,但只能收一次信号;用 <code>select{} 可配合 context 或其他 channel 实现更复杂控制流(比如同时监听健康检查失败)。
- 单信号场景:直接
sig := ,然后走 shutdown 流程 - 多信号区分处理:用
select+switch sig,比如SIGHUP触发重载配置,SIGTERM才真正退出 - 切记:无论哪种,主 goroutine 必须保持运行,直到所有清理完成
最容易被忽略的是信号注册时机和 channel 缓冲——它们不报错,但会让程序在线上「看起来能停,实际一杀就丢数据」。真要验证,别只 Ctrl+C,一定要用 kill -15 $PID 模拟真实环境。

















