
Context 传参必须用 WithValue,但别乱塞东西
Context 不是通用状态容器,WithValue 只该传请求生命周期内只读的元数据(比如用户 ID、trace ID),不是用来替代函数参数或全局配置的。塞进去了,调用链越深越难追溯来源,还容易引发类型断言 panic。
常见错误现象:panic: interface conversion: interface {} is string, not int——因为取值时类型写错了,或者上游根本没存。
- 只存
string、int、自定义 key 类型(推荐用type ctxKey string,避免字符串冲突) - 绝不存指针、结构体、切片等可变对象;更不能存数据库连接、logger 实例等带状态的东西
- 取值前务必用
if val, ok := ctx.Value(myKey).(string); ok { ... }做类型安全检查
Cancel 函数必须显式调用,且只能调用一次
用 context.WithCancel 创建的 cancel 函数,不调就永远不会触发下游的 ctx.Done() 关闭。而重复调用会 panic:panic: context canceled(实际是 runtime 报的 double cancel 错误)。
使用场景:HTTP handler 中启动 goroutine 处理耗时任务,需要在客户端断开时立刻中止;或者超时控制后主动清理资源。
立即学习“go语言免费学习笔记(深入)”;
- cancel 函数建议命名为
cancel,不要改名,否则团队里容易漏掉 defer - 务必在 defer 中调用:
defer cancel(),但要注意:如果函数提前 return,cancel 可能过早触发,需结合业务判断是否延迟调用 - 多个 goroutine 共享同一个 ctx 时,一个 cancel 就全部退出——这是设计目标,不是 bug
Deadline 和 Timeout 的区别直接影响超时精度
WithDeadline 是绝对时间点,WithTimeout 是相对时长。后者底层其实也是转成 WithDeadline,但如果你在高负载下做大量 time.Now().Add(...) 计算,会有微小漂移;而直接传 deadline 更可控。
性能影响:超时触发后,ctx.Err() 返回 context.DeadlineExceeded,这个 error 是预分配的,无内存分配开销;但频繁创建新 Context 本身有少量 GC 压力。
- 服务端统一用
WithTimeout更自然(“最多等 5 秒”) - 分布式链路中传递 deadline(如 gRPC 的
grpc.WaitForReady(false)场景)建议用WithDeadline,避免各节点 Now() 时间不一致导致误差累积 - 测试时别用
time.Sleep等 timeout,用select { case 配合 <code>time.AfterFunc模拟更可靠
HTTP Server 默认不传播 Cancel,得自己接住
Go 1.8+ 的 http.Server 会在 client 断连时关闭 handler 的 Request.Context(),但这个行为依赖底层 TCP 连接状态检测,不是实时的——尤其在长连接、代理层(Nginx)存在时,可能延迟数秒甚至不触发。
容易踩的坑:以为写了 select { case 就万事大吉,结果用户关了页面,后端还在跑 10 秒的数据库查询。
- 对关键 IO 操作(如
db.QueryContext、http.DefaultClient.Do)必须显式传入req.Context() - 自定义中间件里别覆盖
req.Context(),要用req.WithContext(newCtx)包装 - 如果用了反向代理,记得开启
ProxyPreserveHost并确认X-Forwarded-For不干扰连接状态判断










