Go程序优雅退出需用http.Server.Shutdown()配合带超时的context,而非os.Exit()或srv.Close();须用带缓冲channel监听信号,所有goroutine需监听ctx.Done(),多服务关闭应共享cancel context并用WaitGroup协调。

Go 程序不能靠 os.Exit() 或 log.Fatal() 实现优雅退出——它们会跳过清理、杀掉所有 goroutine、丢掉日志缓冲,只适合启动失败场景。
为什么 http.Server.Close() 不是优雅关闭
直接调用 srv.Close() 会立即关闭监听器,并强制中断所有活跃连接。大文件上传、长轮询、流式响应(如 SSE)里的请求会被硬砍断,客户端收到 connection reset 或空响应。这不是“等一等”,而是“全杀光”。Shutdown() 才是标准解法:它先停监听、再等已接受连接自然结束(或超时),且全程受 context.Context 控制。
-
Shutdown()必须传带超时的 context,比如context.WithTimeout(context.Background(), 15*time.Second);用context.Background()会导致永久阻塞 - 不设
ReadTimeout和WriteTimeout,挂起的连接(如客户端不发完 body)可能永远不退出,拖垮 Shutdown -
Shutdown()返回后不代表服务已停——它只是发起通知;需确保主 goroutine 不提前退出(比如别在Shutdown()后立刻 return)
信号没被收到?大概率卡在接收逻辑里
写了 signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT) 却收不到 Ctrl+C?问题不在注册,而在没“等”。signal.Notify 只是把信号转发到 channel,若你没 <-sigChan 或 for range sigChan,程序就直接跑完退出了,根本来不及响应。
- 务必用带缓冲的 channel:
sigChan := make(chan os.Signal, 1),避免信号丢失(尤其 systemd 发 SIGTERM 时可能发得快) - HTTP server 启动必须放 goroutine 里,否则
ListenAndServe()阻塞,信号监听永远卡住 - 不要在 signal handler 里做耗时操作(比如关 DB、写磁盘),只负责触发 shutdown 流程,清理交给主逻辑
goroutine 没退出?不是没通知,是没监听 ctx.Done()
程序 shutdown 后 pprof/goroutine 还剩几百个 worker 卡在 runtime.gopark?说明它们没感知取消信号。Go 没有强制 kill goroutine 的机制,只能靠它自己检查并退出。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 所有长期运行的 goroutine(worker、定时任务、消息消费者)必须接收
ctx context.Context参数,并在循环中select监听<-ctx.Done() - 别用
time.Sleep(5 * time.Second)——它不响应 cancel;改用select { case <-time.After(5 * time.Second): ... case <-ctx.Done(): return } - 数据库查询、HTTP 调用等 I/O 操作,必须用带 context 的版本:
db.QueryRowContext(ctx, ...)、http.DefaultClient.Do(req.WithContext(ctx))
多个服务一起关,顺序和等待怎么协调
HTTP server、gRPC server、后台 worker、DB 连接池……它们有依赖关系(比如 worker 依赖 DB,DB 应该最后关),硬编码关闭顺序容易出错,也难测试。
- 共享一个可 cancel 的 context:
ctx, cancel := context.WithCancel(context.Background()),所有服务启动时都传入它 - 用
sync.WaitGroup计数:每个服务用go func() { defer wg.Done(); service.Run(ctx) }()启动,收到信号后调cancel(),再wg.Wait() - 别复用
WaitGroup实例;每次新一组 goroutine 都要 new 一个,否则Wait()返回后再次Add()会 panic
最容易被忽略的点是:shutdown 超时时间不是拍脑袋定的。它得略大于你最长请求的预期耗时(比如上传 100MB 文件 + 处理耗时共 12 秒,那就设 15 秒),太短会粗暴中断,太长又拖慢发布节奏。还有,Shutdown() 返回 error 时别 panic,记个 log 就行——那只是 context 超时,不是程序崩溃。

















