Golang 怎么实现优雅的优雅退出(信号处理)

5次阅读

go程序需用signal.notify监听sigint和sigterm,通过带缓冲channel接收信号;收到后应触发优雅退出:关闭服务、通知goroutine停止、等待清理完成,而非直接os.exit。

Golang 怎么实现优雅的优雅退出(信号处理)

Go 程序如何监听 SIGINT 和 SIGTERM

Go 本身不自动响应系统信号,必须显式用 signal.Notify 注册监听。最常用的是 SIGINT(Ctrl+C)和 SIGTERMkill -15),两者都应捕获——前者用于本地调试中断,后者是容器/K8s 中的标准终止信号。

关键点:必须用带缓冲的 chan os.Signal,否则可能丢信号;且注册后要确保主 goroutine 不退出,否则程序直接终止,来不及执行清理逻辑。

常见错误:只监听 SIGINT 忽略 SIGTERM,导致在 docker 或 systemd 下无法优雅停止。

示例片段:

立即学习go语言免费学习笔记(深入)”;

sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) <-- 阻塞等待信号 <-- 收到后执行 cleanup()

为什么不能在 signal handler 里直接调用 os.Exit

os.Exit 会立即终止进程,跳过 defer、未完成的 goroutine、sync.WaitGroup 等所有收尾工作,等于放弃“优雅”二字。

正确做法是用一个全局 flag(如 shutdown bool 变量)或 channel 通知其他组件开始退出,并等待它们完成。

典型结构:

  • 收到信号后,关闭监听 socket、设置 shutdown 标志、触发 context.WithCancel
  • http server 调用 srv.Shutdown(),而不是 srv.Close()
  • sync.WaitGroup 等待所有长期 goroutine(如 worker、ticker)主动退出
  • 最后才调用 os.Exit(0) 或自然返回 main

HTTP Server 如何配合 context 实现平滑关闭

Go 1.8+ 的 http.Server.Shutdown() 是核心,但它依赖传入的 context.Context 控制超时。如果没设 timeout,它会无限等待活跃连接结束,拖慢退出速度。

常见陷阱:

  • 忘记给 Shutdown() 传 context,或传了 context.background() 导致无超时
  • Shutdown() 前就关闭 listener,导致新连接被拒但旧连接仍卡住
  • 没设置 ReadTimeout/WriteTimeout,导致慢客户端拖死整个 shutdown 流程

推荐写法:

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil {     log.Printf("HTTP shutdown error: %v", err) }

goroutine 泄漏是优雅退出失败的最常见原因

很多服务启动了 ticker、worker pool、长轮询 goroutine,但没提供退出通道。一旦主流程想停,这些 goroutine 还在后台跑,导致程序 hang 住或资源泄漏。

解决方案统一:每个长期 goroutine 都要监听一个 done chan Struct{}context.Context.Done()

例如:

go func() {     ticker := time.NewTicker(5 * time.Second)     defer ticker.Stop()     for {         select {         case <-ticker.C:             doWork()         case <-done: // 外部通知退出             return         }     } }()

注意:不要用 time.After 替代 ticker 做周期任务,它每次都会新建 timer,且无法主动 stop。

复杂点在于:有些第三方库(如某些 DB driver、gRPC client)内部也启了 goroutine,需查文档确认是否支持 context 取消或 Close 方法——漏掉一个,就可能卡住整个退出流程。

text=ZqhQzanResources