Go信号处理需构建监听→通知→清理→退出完整链,signal.Notify仅转发信号到带缓冲channel,cleanup须由主goroutine执行并等待goroutine自然结束,避免os.Exit和goroutine泄漏。

Go 语言信号处理不是“注册个回调就完事”,而是要主动构建一个响应链:监听 → 通知主逻辑 → 执行清理 → 安全退出。漏掉任一环,程序就可能僵死、丢数据或被强制 kill。
signal.Notify 怎么用才不踩坑
很多人以为 signal.Notify 是个“设置钩子”的函数,其实它只是把系统信号**转发到 channel**,不自动触发任何逻辑。它本身不阻塞,也不执行清理——这些全得你手动安排。
- 必须传入带缓冲的 channel(至少
make(chan os.Signal, 1)),否则首次信号会丢失(因为没 goroutine 在等) - 只注册真正需要响应的信号:
syscall.SIGINT和syscall.SIGTERM是底线;syscall.SIGHUP要加就加,但得配套实现配置重载逻辑 - 别注册
syscall.SIGKILL(信号 9)——它根本捕获不到,写了也白写 - 避免在
signal.Notify后立刻主线程阻塞,这会让主逻辑无法响应其他事件(比如 HTTP 请求)
为什么 cleanup 不能放在 signal handler goroutine 里直接调用 os.Exit
看似省事的写法:go func() { ,实际藏着两个硬伤:一是 <code>os.Exit 会立即终止进程,跳过 defer、关闭 http.Server 等标准退出流程;二是如果 cleanup() 里有阻塞操作(比如等待数据库连接池归还),整个进程就卡住不动了。
- 正确做法是用一个
donechannel 或sync.Once触发清理,让主 goroutine 控制退出节奏 - HTTP 服务要先调用
server.Shutdown(ctx),再关 DB、删临时文件,最后return或os.Exit - 如果清理耗时较长(>5s),建议设超时 context,避免无限等待
优雅退出时如何避免“假退出”——goroutine 泄漏问题
常见现象:收到 SIGTERM 后打印了“cleaning up”,程序却没退出,ps 还能看到进程,或者日志里反复出现 panic: send on closed channel。本质是还有 goroutine 在往已关闭的 channel 发数据,或没等它们自然结束就退出了。
立即学习“go语言免费学习笔记(深入)”;
- 所有长期运行的 goroutine(比如心跳、轮询、后台 worker)都应接收一个
context.Context,并在后自行退出 - 不要依赖“main 函数 return”来终结 goroutine;要用
sync.WaitGroup显式等待关键 goroutine 结束 - 避免在清理阶段启动新 goroutine(比如异步上报错误),除非你确保它能在退出前完成
信号处理最易被忽略的点,是它和程序生命周期的耦合深度——不是加几行代码就叫“支持 SIGTERM”,而是每个资源打开、每个 goroutine 启动,都要预设好“被中断时怎么收尾”。否则,SIGTERM 只是换了个方式 kill -9。


















