Go程序在Linux中优雅处理中断信号需监听SIGINT和SIGTERM,通过带缓冲channel接收信号;服务启动需置于goroutine,主线程等待信号;用带超时的context调用srv.Shutdown()关闭服务器并等待请求结束;后台goroutine须共用同一ctx监听done并执行清理。

Go程序在Linux中优雅处理中断信号,核心是用os/signal监听可捕获信号(主要是SIGINT和SIGTERM),触发清理逻辑并控制退出节奏,而不是让进程被强制终止。
只监听可响应的关键信号
Linux中真正能被Go程序捕获并用于优雅退出的信号只有两个:
- SIGINT(信号2):用户按 Ctrl+C 触发,代表手动中断请求
-
SIGTERM(信号15):系统或容器管理器(如
kill 1234、docker stop)发出的标准终止信号
不要尝试监听SIGKILL(9)或SIGSTOP——它们不可捕获,操作系统会直接终止进程,任何清理代码都来不及执行。
推荐写法是带缓冲的 channel:
sigCh := make(chan os.Signal, 1)signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
服务启动与信号等待必须分离
HTTP服务器(如http.ListenAndServe())本身是阻塞调用。如果直接在主线程里运行它,信号监听就无法生效。
正确做法是:
- 把服务启动放到 goroutine 中
- 主线程保持阻塞,专门等待信号到来
同时要忽略http.ErrServerClosed——这是srv.Shutdown()正常调用后ListenAndServe()返回的错误,不是异常。
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
log.Fatal(err)
}
}()
用 Shutdown 配合超时 context 控制退出节奏
srv.Shutdown()是优雅退出的关键动作,它会:
- 立即关闭监听器,拒绝新连接
- 等待已有请求自然结束(或超时)
它本身不阻塞到底,必须配合带超时的 context:
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)defer cancel()
if err := srv.Shutdown(ctx); err != nil && err != http.ErrServerClosed {
log.Printf("shutdown error: %v", err)
}
超时时间不能拍脑袋定,应略大于业务中最长请求耗时(比如文件上传、长轮询等),否则可能强行中断还在处理的请求。
同步退出所有后台 goroutine
数据库连接、消息消费者、定时任务等长期运行的 goroutine,必须统一响应退出信号:
- 共用同一个
context.Context(或done chan struct{})通知退出 - 在
select中监听ctx.Done(),而不是单次检查ctx.Err() - 清理操作(如
db.Close()、redis.Close())放在case 分支里执行
这样能确保资源及时释放,避免连接泄漏或状态不一致。


















