Go 高手(20):信号处理与优雅关闭——os/signal、HTTP Shutdown
更新时间:2026-09-01。本文是
languages/go/intermediate/高手层第 20 篇,接 定时器与资源管理。生产级 Go 服务需要优雅关闭:收到 SIGINT/SIGTERM 后,不再接受新请求,处理完正在处理的请求,释放资源,然后退出。本文讲完整流程。
本文要回答的问题
- 什么是优雅关闭?为什么需要?强制 kill 有什么问题?
- 怎么用
os/signal捕获信号? - HTTP 服务的
Shutdown怎么用?怎么配合 context 取消? - 正在处理的 goroutine 怎么让它们退出?
一、什么是优雅关闭
强制 kill(kill -9)直接终止进程,会导致:
- 客户端收到不完整响应
- 数据库事务没提交,数据不一致
- 打开的文件没有关闭,文件描述符泄漏
- 缓存没落地,数据丢失
优雅关闭:
- 收到关闭信号 → 停止接收新请求
- 处理完正在处理的请求 → 释放资源
- 所有 goroutine 都退出 → 程序退出
二、捕获信号:os/signal
go
package main
import (
"os"
"os/signal"
"syscall"
)
func main() {
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
// 等待信号
<-sigChan
// 开始优雅关闭...
}syscall.SIGINT:Ctrl+C 中断syscall.SIGTERM:kill默认信号,服务管理框架(systemd、k8s)发这个signal.Notify把收到的信号发送到 channel
三、完整示例:HTTP 服务优雅关闭
go
package main
import (
"context"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
srv := &http.Server{
Addr: ":8080",
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(5 * time.Second) // 模拟长请求
w.Write([]byte("hello"))
}),
}
// 启动服务
go func() {
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
log.Fatalf("ListenAndServe failed: %v", err)
}
}()
// 等待信号
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan
log.Println("Shutting down server...")
// 给 5 秒时间处理正在处理的请求
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatalf("Shutdown failed: %v", err)
}
log.Println("Server exited")
}流程:
- 启动 HTTP 服务
- 主 goroutine 等待信号
- 收到信号 → 调用
Shutdown Shutdown:停止监听,处理完正在处理的连接,然后退出- 超时了,强制退出
四、context 取消传递给 goroutine
如果你的服务有 background goroutine,需要把取消信号传递给它们:
go
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// 启动 background goroutine
go worker(ctx, ch)
// 等待信号
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan
// 触发取消,让 background goroutine 退出
cancel()
// 等待 worker 退出
wg.Wait()go
func worker(ctx context.Context, wg *sync.WaitGroup) {
defer wg.Done()
for {
select {
case <-ctx.Done():
// 收到取消信号,清理退出
return
default:
// 干活
}
}
}五、超时控制
go
// 超时 5 秒
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// 等待关闭完成
err := srv.Shutdown(ctx)
if err != nil {
log.Printf("Shutdown timed out: %v", err)
// 强制退出
os.Exit(1)
}超时的作用:如果 5 秒后还有请求没处理完,强制关闭,避免一直等待。
六、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| 忘记调用 Shutdown | ListenAndServe 直接退出,所有连接都断开 | Shutdown 才会优雅处理正在处理的连接 |
| 不设置超时 | 长请求一直不退出,程序不退出 | 设置一个合理的超时(5-30 秒) |
| background goroutine 不检查 ctx.Done() | goroutine 泄漏,程序一直不退出 | 用 context 传递取消信号 |
| 收到信号立刻退出 | 正在处理的请求被强制断开 | 先停止监听,等待正在处理的请求完成 |
相关与延伸
进阶层到此告一段落,你已经覆盖了 Go 核心中阶知识。接下来可以看专家层 pprof 性能剖析;context 取消机制,见 context 包;HTTP 服务,见 net/http 深入。
一句话总结
Go 信号处理与优雅关闭:用 signal.Notify 捕获 SIGINT 和 SIGTERM;HTTP 服务用 Shutdown 配合 context 超时,停止接收新请求,处理完正在处理的;background goroutine 用 context.WithCancel 传递取消信号,收到信号后 cancel() 让 goroutine 退出;设置合理超时,避免一直等待不退出。