Go语言优雅退出需捕获SIGINT/SIGTERM信号,启动HTTP服务于goroutine中,用srv.Shutdown()配合超时context关闭监听并等待请求完成,同步通知后台goroutine退出并清理资源。

Go语言中实现优雅退出,核心是捕获可中断信号(如 SIGINT 和 SIGTERM),触发资源清理,并给正在处理的请求留出完成时间。不能依赖默认行为——http.ListenAndServe() 不监听信号,直接杀进程会导致连接重置、日志截断、事务不完整。
监听并响应关键信号
使用 os/signal 包注册信号监听,重点关注两个可捕获、应被响应的信号:
-
SIGINT:用户按
Ctrl+C触发,代表手动中断请求 -
SIGTERM:系统或容器管理器(如
kill、docker stop)发出的标准终止信号
避免监听 SIGKILL(信号 9)——它不可捕获,操作系统强制终止,无法做任何清理。
推荐写法是带缓冲的 channel:
立即学习“go语言免费学习笔记(深入)”;
sigCh := make(chan os.Signal, 1)signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
启动服务与信号等待分离
HTTP 服务必须在 goroutine 中启动,主线程保持阻塞以等待信号到来。否则,ListenAndServe() 阻塞后,信号监听逻辑无法执行。
同时要忽略 http.ErrServerClosed——这是 Shutdown() 正常关闭时 ListenAndServe() 返回的错误,不是异常。
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
log.Fatal(err)
}
}()
调用 Shutdown 并控制超时
srv.Shutdown() 是优雅退出的关键动作,它会:
- 立即关闭监听器,拒绝新连接
- 等待已有请求自然结束(或超时)
它本身不阻塞到底,必须配合带超时的 context.Context:
defer cancel()
if err := srv.Shutdown(ctx); err != nil && err != http.ErrServerClosed {
log.Printf("shutdown error: %v", err)
}
超时时间需略大于业务中最长请求耗时(例如文件上传、长轮询),不能拍脑袋设成 1 秒或 5 秒。
同步退出所有后台 goroutine
数据库连接、消息消费者、定时任务等长期运行的 goroutine,必须统一响应退出信号:
- 用同一个
context.Context(或done chan struct{})通知退出 - 在
select中监听ctx.Done(),而不是用if ctx.Err() != nil单次判断 - 清理操作(如
db.Close()、redis.Close())放在case <-ctx.Done():分支里,确保及时执行 - 用
sync.WaitGroup等待所有 goroutine 完成,避免主函数提前退出


















