signal.notify未指定信号类型时默认只监听sigint和sigkill(后者不可捕获),收不到sigterm;生产环境需显式注册syscall.sigint、syscall.sigterm、syscall.sighup等信号,并确保main不立即退出。

signal.Notify没传信号类型,收不到SIGTERM?
默认不监听 SIGTERM,只监听 os.Interrupt(即 SIGINT)和 os.Kill(不可捕获,仅触发 os.Exit)。生产环境必须显式列出要处理的信号。
- 错误写法:
signal.Notify(sigChan)—— 看似省事,实际漏掉关键信号 - 正确写法:
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) -
syscall.SIGHUP要额外加:终端断开、nohup启动、systemd reload 都靠它,但 Go 默认不注册 - 容器或 systemd 下若没配
KillMode=process,SIGTERM根本到不了进程,得先查父进程行为,别急着改代码
注册完信号就继续跑 main,程序秒退?
signal.Notify 只是“登记”,不阻塞、不等待。main 函数执行完就退出,信号来了也来不及处理。
- 必须用
阻塞主 goroutine,等信号到来才往下走 - 禁用
time.Sleep(1000 * time.Hour)或for {}:前者不可靠,后者吃满 CPU - 如果主逻辑本身是长循环(比如 HTTP server),别在循环外单独等信号;应把信号接收逻辑嵌入 select 中,配合 context 控制退出
- 常见坑:goroutine 里启动了 server,但 main 函数没等它,直接返回——整个进程挂了,server 连启动都没完成
收到 SIGTERM 后 HTTP server 关不掉?
srv.Close() 是暴力关闭,会中断正在处理的请求;srv.Shutdown() 才是优雅退出,但它依赖 context 超时和内部资源清理是否配合。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 调用
srv.Shutdown(ctx)前,必须已调用cancel()触发 ctx.Done() - 超时值建议设为部署平台终止宽限期减 5 秒(如 K8s 的
terminationGracePeriodSeconds: 30→ 代码里用25s) - 真正卡住 shutdown 的,往往不是 server 本身,而是中间件里漏掉的 goroutine:比如一个没加
ctx.Done()判断的定时轮询、channel receive 或无休止 for-select - 用
sync.WaitGroup记录所有活跃 worker,cancel()后wg.Wait(),确保它们都退出了再让 main 结束
为什么本地 Ctrl+C 正常,上线后 kill -15 没反应?
信号根本没进到 Go 进程里,不是代码问题,是运行环境拦截或转发失败。
- Docker 默认不转发
SIGTERM:加--init参数(或用tini),避免 init 进程吞掉信号 - systemd 服务文件里必须写明:
KillSignal=SIGTERM和KillMode=process;否则它直接杀 cgroup,跳过 Go 的 signal handler - 用
strace -e trace=rt_sigprocmask,rt_sigaction,kill -p $(pidof yourapp)看信号是否抵达进程,比猜更准 - 守护进程想收
SIGHUP?得先成为 session leader:syscall.Setpgid(0, 0)或调setsid(),否则内核根本不发
信号处理最易被忽略的点不在 Go 代码里,而在进程怎么被启动、谁在发信号、信号路径上有没有被截胡。写对 signal.Notify 只是起点,确认信号真能抵达、能被阻塞等待、能触发完整退出链路,才算闭环。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










