必须显式调用signal.notify并监听syscall.sigint和syscall.sigterm才能捕获信号,否则进程直接终止;需在主业务启动前注册、使用带缓冲通道并持续读取,收到信号后应触发优雅关闭而非立即退出。

Go 中用 signal.Notify 捕获 SIGINT/SIGTERM 是最直接方式
Go 标准库的 signal.Notify 是唯一推荐的信号监听入口,它把操作系统信号转为 Go channel 事件,避免了阻塞和竞态。别试图用 syscall 手动轮询或绑定 C 函数——既难维护又容易漏掉信号。
典型用法是创建一个 chan os.Signal,然后调用 signal.Notify 注册关注的信号:
sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
注意两点:channel 必须带缓冲(至少 1),否则第一个信号就可能丢失;SIGINT 和 SIGTERM 是服务类程序最该响应的两个信号,SIGHUP 视场景决定是否加入。
优雅退出的关键不是“收到信号就退出”,而是“收到后触发清理再退出”
很多代码写成 ,这不算优雅——数据库连接没关、HTTP server 没 shutdown、goroutine 没等待,资源泄漏就发生了。
真正要做的,是在收到信号后启动 shutdown 流程,并给关键组件留出超时时间:
-
http.Server.Shutdown()要传入 context,且需提前设置好ReadTimeout/WriteTimeout - 自定义的 long-running goroutine 应监听同一个
context.Context,并在ctx.Done()时退出 - 数据库连接池建议调用
db.Close(),而不是依赖 GC - 外部资源(如文件句柄、gRPC 连接)必须显式释放
别依赖 defer —— 它只在函数返回时执行,而主 goroutine 收到信号后应主动控制流程。
Go语言(Golang)1.26.0版本提供 Go 官方 Windows amd64 MSI 安装包下载入口,版本号 1.26.0,可用于旧项目维护、兼容性测试和指定版本开发环境配置。
多个信号并发到达时,signal.Notify 不会丢信号但 channel 可能堵住
signal.Notify 保证每个信号都发到 channel,但若 channel 没及时消费,后续信号会被阻塞(因缓冲区满)。常见错误是只读一次 就结束,结果第二次 <code>kill -TERM 没反应。
正确做法是启动一个单独 goroutine 持续读取:
go func() {
for range sigChan {
log.Println("shutdown signal received")
shutdown(ctx) // 启动清理
return
}
}()
这样即使连续发多次 SIGTERM,也只触发一次 shutdown;若想支持“强制终止”,可在 shutdown 内部加二次信号监听,但多数服务不需要。
测试信号处理逻辑不能靠真实 kill,得用 os.Interrupt 或 syscall.Kill
本地调试时用 Ctrl+C 可以,但单元测试里没法按键盘。可靠方式是用 syscall.Kill(syscall.Getpid(), syscall.SIGTERM) 向自身发信号,或更安全地用 os.Interrupt(仅限 Unix-like 系统)。
注意:syscall.Kill 在 Windows 上行为不同,CI 环境若跨平台,建议用 exec.Command("kill", "-TERM", strconv.Itoa(os.Getpid())) 替代;但更稳妥的做法是把信号接收和业务逻辑解耦,用 channel 注入 shutdown 事件来测试,避开系统调用。
实际部署时,Kubernetes 的 terminationGracePeriodSeconds 和 systemd 的 TimeoutStopSec 都会影响你预留的 shutdown 时间窗口,别假设总能有 30 秒——关键路径最好控制在 5 秒内完成清理。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










