Go程序需用signal.Notify显式监听syscall.SIGINT和syscall.SIGTERM信号,通过带缓冲channel接收并触发清理;必须在业务启动后监听,清理时调用server.Shutdown配合context超时,避免直接os.Exit。

Go 程序如何监听 SIGINT 和 SIGTERM
绝大多数 Go 服务需要响应系统信号来优雅退出,核心就是用 signal.Notify 把指定信号转发到一个 chan os.Signal。注意:必须显式传入要监听的信号,不传或传 os.Interrupt(等价于 SIGINT)会漏掉 SIGTERM——Kubernetes 或 systemd 杀进程时发的就是它。
常见错误是只写:
sigChan := make(chan os.Signal, 1) signal.Notify(sigChan) // ❌ 监听所有信号?实际只监听默认的 SIGINT 和 SIGQUIT
正确做法是明确列出:
- 监听
SIGINT(Ctrl+C)和SIGTERM(kill -15)就够了,除非你真需要处理SIGHUP或SIGUSR1 - 通道缓冲区设为 1 即可,多个信号连续到达时,未读取的后续信号会被丢弃——这反而是安全设计,避免阻塞主流程
- 不要在
signal.Notify后立刻select等待,得先启动业务逻辑(比如 HTTP server),否则程序一启动就卡住
为什么 signal.Stop 很少用,但 signal.Reset 有时必须调
signal.Stop 只对之前用同一通道调用过 signal.Notify 的情况有效;而 Go 运行时内部对每个信号维护一个全局监听器列表,多次调用 signal.Notify 用不同通道监听同一信号,会导致该信号被同时发给所有通道——可能引发重复处理。
典型踩坑场景:
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 测试中反复启停服务,每次都在新 goroutine 里
signal.Notify(ch, syscall.SIGTERM),结果一次kill触发多个 shutdown 流程 - 解决办法不是靠
signal.Stop(容易漏调或错对象),而是改用signal.Reset(syscall.SIGTERM)清空运行时对该信号的所有监听,再重新Notify -
signal.Reset是全局操作,会影响其他包(如net/http内部用的SIGPIPE处理),所以只应在自己完全掌控信号生命周期时使用,比如主程序入口
优雅退出时如何等待 HTTP server 关闭完成
收到信号后不能直接 os.Exit(0),得先关闭 server 并等正在处理的请求结束。但 http.Server.Shutdown 本身会阻塞,如果放在主 goroutine 里,就无法响应二次信号(如 SIGKILL 强杀)。
实操要点:
- 用
context.WithTimeout给Shutdown加超时(比如 10 秒),超时后强制os.Exit(1) - 把
Shutdown放进独立 goroutine,主 goroutine 继续监听信号——这样第二次SIGTERM可以触发立即退出 - 别依赖
Server.Close(它不等长连接),必须用Shutdown;且调用前要确保所有 handler 都支持 context 取消(比如用r.Context().Done()检查)
Windows 下 SIGTERM 不可用,怎么办
Windows 没有 SIGTERM,syscall.SIGTERM 在 Windows 上是 0,signal.Notify 会静默失败。但 Windows 有 os.Interrupt(对应 Ctrl+C)和 syscall.SIGINT,此外还可监听系统控制台事件(如窗口关闭)。
跨平台方案建议:
- 统一监听
os.Interrupt和syscall.SIGTERM,在 Windows 上SIGTERM被忽略,不影响逻辑 - 如果需响应 Windows 服务停止或窗口关闭,得用
golang.org/x/sys/windows调用SetConsoleCtrlHandler,但这已超出标准 signal 包能力,属于平台特例 - CI/CD 或容器环境几乎全是 Linux,优先保障
SIGTERM行为正确;本地开发用 Ctrl+C 测试足够
信号处理真正的复杂点不在注册,而在 shutdown 阶段的资源协同:数据库连接池、gRPC 客户端、后台定时任务……这些都要各自提供可取消的关闭接口,并在主信号处理流程里按顺序触发。没暴露关闭方法的第三方库,往往就是优雅退出的断点。

















