Go 里 context 取消传播的一个细节

2026-05-12 · 编程

线上有个服务偶尔 goroutine 数量往上飙,最后定位到一处对下游的调用在请求结束后没被取消。复盘下来是个很常见的疏忽。

最小复现

func handler(w http.ResponseWriter, r *http.Request) {
    // 错误:用了 context.Background(),和请求生命周期脱钩
    go doWork(context.Background())
}

func doWork(ctx context.Context) {
    // 请求早就返回了,这个还在慢慢跑
    resp, _ := http.Get("https://slow.example.com/")
    _ = resp
}

问题在于 context.Background() 永不取消。请求处理完了,但派生出去的这个 goroutine 拿着一个永远活着的 context,下游一慢就堆积。

正确做法

要么直接用 r.Context(),要么在需要脱离请求生命周期时用 context.WithTimeout 兜一个上限:

ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
defer cancel()
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)

加上超时之后,那条泄漏曲线立刻就平了。教训是:任何跨网络的调用都应该带一个会取消的 context,别图省事传 Background。

← 返回首页