Go 小工具怎么优雅退出:信号处理与资源清理

举报
yd_271078175 发表于 2026/11/02 10:09:17 2026/11/02
【摘要】 一个跑在后台的小工具,收到 Ctrl+C 或者被 kill 的时候,最理想的状态是:把手上这段活干完,关掉连接,写一行日志,然后退出。默认行为是立刻死掉,什么都来不及做。

一个跑在后台的小工具,收到 Ctrl+C 或者被 kill 的时候,最理想的状态是:把手上这段活干完,关掉连接,写一行日志,然后退出。默认行为是立刻死掉,什么都来不及做。

这篇记一下用标准库把这件事做对需要几步。

一、信号不是异常,别用 recover 去接

新手常见写法是在 main 里 defer recover(),指望它接住 Ctrl+C。接不住:信号是操作系统发给进程的,不是 Go 的 panic。

正确方式是 os/signal:

ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()

NotifyContext 返回的 ctx 在收到信号时被取消,接下来所有支持 context 的操作都会跟着退出来。比手动注册 channel 干净,也不用自己写 select。

二、把 ctx 一路传下去

光有 ctx 不够,得让它真的能中断正在做的事。判断标准很简单:所有可能阻塞的调用,都应该收一个 ctx。

req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
resp, err := client.Do(req)

数据库、子进程、sleep 也一样。一项漏了,退出时就会卡在那一项上,等超时。

还有一个细节:time.Sleep 不能被打断,要换成下面这个:

select {
case <-ctx.Done():
    return ctx.Err()
case <-time.After(d):
}

定时任务里这个很常见——退出时正好在退避等待,于是要等满 5 秒才退。

三、清理动作放在 defer 里,但要能被执行

defer 只在函数正常返回或 panic 时执行。如果最后走的是 os.Exit,defer 一律不跑。

这就是为什么 main 里不要直接 os.Exit,而是把逻辑收进一个函数:

func main() {
    if err := run(); err != nil {
        fmt.Fprintln(os.Stderr, "错误:"+err.Error())
        os.Exit(1)
    }
}

run 里的 defer 正常执行,清理做完之后再决定退出码。

四、给清理留一个兜底时间

收到第一个信号后开始清理,这时候如果清理本身卡住了,进程会僵在那里。用户再按一次 Ctrl+C,期望的是「别管了,直接退」。

标准做法是第一个信号触发优雅退出,第二个信号强制退出。NotifyContext 之后 stop() 掉,信号就恢复默认行为:

ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()

go func() {
    <-ctx.Done()
    stop() // 之后再收到信号,就是默认的「立刻死」
}()

再配一个超时兜底,防止某些清理逻辑没有 context 可用:

select {
case <-done:
case <-time.After(10 * time.Second):
    slog.Warn("清理超时,强制退出")
}

五、退出时写清楚「为什么退」

这一条和优雅无关,但同样重要。退出日志里至少要能看出是被信号打断的、还是任务失败的:

slog.Info("收到退出信号,开始清理")
...
slog.Info("清理完成,退出", "reason", ctx.Err())

定时任务的日志是事后看的。一条 exit 什么都没说,一条 exit reason=context canceled 能立刻区分「被人停了」和「自己崩了」。

六、小结

四件事:用 NotifyContext 接信号、把 ctx 传到底、清理放 defer 且避开 os.Exit、第二次信号强制退出。

代码量不大,但每一条漏掉,都会在实际运维时以「进程退不干净」的形式还回来,而且往往是在半夜。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。