Go程序需用signal.Notify监听os.Interrupt和syscall.SIGTERM信号,通过带缓冲的channel(推荐大小为1)接收,并在goroutine中select处理,确保执行清理逻辑后再退出。

Go 程序怎么优雅退出?用 signal.Notify 捕获 os.Interrupt 和 os.Kill
Go 本身不会自动响应 Ctrl+C 或 kill -15,必须显式监听信号才能触发清理逻辑。核心就是 signal.Notify —— 它把系统信号转成 Go 的 channel 事件,后续靠 select 接收。
常见错误是只监听 os.Interrupt(对应 Ctrl+C),漏掉 syscall.SIGTERM(kill -15),导致容器或 systemd 环境下无法正常退出。
-
signal.Notify第一个参数是接收信号的chan os.Signal,第二个及以后是你要监听的信号类型,至少要包含os.Interrupt和syscall.SIGTERM - 不要在
main函数里直接os.Exit(0),应先执行资源释放(如关闭数据库连接、写完日志),再退出 - 监听 channel 必须在 goroutine 里阻塞等待,否则主 goroutine 退出后整个程序就结束了,信号根本来不及处理
为什么 signal.Notify 要配 make(chan os.Signal, 1)?缓冲区大小影响什么?
缓冲区设为 1 是最常用也最安全的选择。信号可能在你还没来得及 select 就已到达,如果没有缓冲,信号会丢失 —— 尤其是快速连续发两次 kill -15,第二次就收不到。
设成 0(无缓冲)容易出问题;设太大(比如 100)没必要,还可能掩盖逻辑缺陷(比如没及时消费信号)。
立即学习“go语言免费学习笔记(深入)”;
- 推荐写法:
sigChan := make(chan os.Signal, 1) -
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)后,立刻启动 goroutine 处理:go func() { - 如果需要支持多次信号(比如第一次优雅退出,第二次强制退出),才考虑用更大缓冲或多个 channel 分流
监听 SIGUSR1 做热重载时,signal.Notify 有哪些坑?
SIGUSR1 和 SIGUSR2 常用于通知进程重载配置或打印 debug 信息,但它们在 Windows 上不可用(Go 会忽略),且不同 Unix 系统语义略有差异,不能依赖默认行为。
- 必须显式导入
"syscall"包才能使用syscall.SIGUSR1 - Linux 下
SIGUSR1默认终止进程,所以一定要用signal.Notify拦截,否则发一次就挂了 - 不要在信号 handler 里做耗时操作(如读大文件、网络请求),建议只发通知到主逻辑,由主 goroutine 统一处理
- 测试时用
kill -USR1 $(pidof yourapp),别用kill -10这种数字写法,可读性差还容易输错
goroutine 泄漏风险:为什么 signal.Notify 之后不能直接 select 等待?
很多人写成 select { case 放在 <code>main 末尾,看似没问题,但如果主逻辑里还有其他 goroutine 在跑(比如 http server、定时任务),它们可能还在运行,而 main 已退出,造成“伪退出”—— 进程没了,但后台任务没清理干净。
- 正确做法是:用
sync.WaitGroup或context.WithCancel管理所有子 goroutine 生命周期 - 收到信号后,先调用
cancel()或wg.Done(),再等几秒(比如time.After(5 * time.Second)),最后才os.Exit -
http.Server.Shutdown()这类方法必须配合 context 使用,否则可能卡住
信号监听本身很简单,难的是让整个程序真正“停稳”。多数线上故障不是信号没收到,而是退出路径里漏了某个 goroutine 或连接池。


















