Context传值必须用WithValue且仅存少量请求级元数据;业务数据应建结构体而非塞入Context;key需自定义类型防冲突;超时需配合cancel并传递ctx至阻塞操作。

Context 传值必须用 WithValue,但别乱塞业务数据
Context 不是通用状态容器,WithValue 只能存少量、生命周期与请求一致的元数据(比如用户 ID、请求追踪 ID)。业务逻辑需要的数据,该建结构体就建结构体,别往 context.Context 里塞 map 或 struct。否则调试时根本分不清哪层塞的、谁读的、有没有被覆盖。
常见错误:在中间件里用 ctx = context.WithValue(ctx, "user", u),然后下游层层 type assert,一旦 key 写错或类型不匹配,运行时报 panic: interface conversion: interface {} is nil, not *User。
- key 必须是自定义类型(比如
type ctxKey string),避免字符串冲突 - 只存不可变值,别传指针或可修改对象
- 用
value, ok := ctx.Value(myKey).(MyType)安全断言,别直接强制转换
超时控制要配 cancel,光设 Deadline 不够
context.WithTimeout 和 context.WithDeadline 看似设了时间,但真正起作用靠的是底层自动调用 cancel()。如果协程没监听 ctx.Done() 或没把 ctx 传给 I/O 操作(比如 http.Client、sql.DB.QueryContext),超时就只是个摆设。
典型问题:手动启 goroutine 调用第三方 API,但忘了把 ctx 传进去,或者用了老版不支持 context 的 SDK,结果 timeout 触发后协程还在跑,资源泄漏。
立即学习“go语言免费学习笔记(深入)”;
- 所有阻塞操作优先选
xxxContext版本函数(如net/http.Client.Do→DoContext) - 自己写的 long-running loop 必须检查
select { case - 记得 defer cancel(),否则 timer 不释放,可能引发内存泄漏
不要在 Context 里传取消信号以外的“控制流”
有人用 WithValue 传开关(比如 ctx = context.WithValue(ctx, "skipAuth", true)),这会让调用链变成隐式依赖——下游不知道自己行为被谁改了,测试难、排查难、重构更难。Context 的设计目标只有两个:取消和超时。其他控制逻辑,应该显式作为函数参数或配置项传递。
真实踩坑案例:A 服务透传 B 服务的 ctx,B 在里面塞了 "retryCount",结果 A 的重试逻辑误读该值,导致重试次数翻倍;后来 B 改了 key 名,A 就静默退化成默认值,线上才暴露。
- 任何需要分支逻辑的参数,都该是函数入参,不是 context value
- 日志字段、监控标签等可读性需求,用 middleware 提前提取并注入 logger,别靠 runtime 查 context
- HTTP header 映射到 context 是合理场景,但仅限于 request-scoped 元信息
父子 Context 链路断开最常见于 goroutine 启动时机
写 go fn(ctx) 时,如果 ctx 是外层函数参数,而你没把它作为参数传进 fn,那这个 goroutine 就拿不到父级 cancel 信号。更隐蔽的是:你在某个 if 分支里新建了 context.WithCancel(parent),但没把新 ctx 传下去,结果子协程永远收不到 cancel。
现象:HTTP 请求已返回,但后台 goroutine 还在跑,pprof 看到一堆 “goroutine waiting on channel”,其实是卡在 却没人 close。
- 启动 goroutine 时,第一参数必须是
ctx,且确保它来自上游、未被意外替换 - 避免在 goroutine 内部调用
context.WithCancel后忘记传出去或 defer cancel - 用
go vet -race和go tool trace查未关闭的 goroutine,比加 log 更快定位断链点


















