必须接收ctx context.context的函数包括:http.client.do、sql.db.querycontext、grpc.clientconn.invoke、watchconfig(ctx)等可能阻塞、发起i/o或启动需受控goroutine的函数;纯内存计算函数无需接收ctx。

Go微服务里,Context不是“加不加”的问题,而是“在哪加、怎么传、怎么关”的问题——加错位置或漏调cancel(),轻则超时失效,重则goroutine泄漏。
哪些函数必须接收ctx context.Context?
只在函数可能阻塞、发起I/O或启动需受控goroutine时才需要显式接收ctx。
-
http.Client.Do、sql.DB.QueryContext、grpc.ClientConn.Invoke这类等待外部响应的调用 - 启动轮询或监听的长期goroutine,且该goroutine需响应取消(如
watchConfig(ctx)) - 封装了上述行为的业务函数,比如
fetchOrderDetails(ctx, orderID)内部做了HTTP+DB双调用 - 纯内存计算函数(如
calculateFee(items)、validateJSON(b))传ctx是冗余的,破坏可测试性
HTTP handler里为什么不能用context.Background()?
handler收到请求时,r.Context()已自带生命周期控制(含超时、取消),硬写context.Background()等于主动放弃所有控制权。
- 常见错误:
func handler(w http.ResponseWriter, r *http.Request) { ctx := context.Background(); db.QueryRowContext(ctx, ...) }→ 调用方无法中断该查询 - 正确做法:直接用
r.Context(),并确保下游调用链全程透传,如db.QueryRowContext(r.Context(), ...) - 若需设置更短超时,用
context.WithTimeout(r.Context(), 2*time.Second),而非替换根ctx
context.WithTimeout和context.WithDeadline怎么选?
选哪个取决于你控制的是“持续时间”还是“绝对时间点”。
-
context.WithTimeout:适合“最多等5秒”,比如下游RPC调用、缓存查询 -
context.WithDeadline:适合“必须在2026-06-13T08:30:00Z前完成”,比如分布式锁续期、定时任务保活 - 关键细节:
WithDeadline传入已过期时间,ctx.Err()立即返回context.DeadlineExceeded - 高频遗漏:
defer cancel()必须写,否则timer不释放,goroutine泄漏 - 禁止对同一
ctx多次调用WithTimeout——子ctx deadline会叠加,逻辑易误判
context.WithValue能存什么?不能存什么?
仅限传递轻量、请求级、不可变的元数据,比如traceID、userID、locale。
- 能存:
ctx = context.WithValue(r.Context(), "traceID", r.Header.Get("X-Trace-ID")) - 不能存:结构体指针、大对象、可变状态(如
*sync.Mutex)、业务实体(如*User) - 类型安全建议:用自定义key类型(如
type ctxKey string),避免字符串key冲突 - 值查找成本低但非零,频繁调用
ctx.Value()需注意性能影响
最常被忽略的其实是cancel()调用时机——它必须在子ctx不再需要时立刻执行,而不是等到函数return之后;一旦漏掉,timer goroutine就永远挂着,泄漏会随QPS线性增长。











