context 不是自动透传的魔法,漏传、不更新、不监听即失效;ctx.value 取不到值需检查 key 类型(须用私有结构体)和赋值链路(必须显式赋值并传递新 ctx);withcancel/withtimeout 的 cancel 函数调用需谨慎,避免泄漏;监听 ctx.done() 时必须配合 ctx.err() 判断。

Context 不是自动透传的魔法,漏传、不更新、不监听,就等于没用。
ctx.Value 取不到值?先检查 key 类型和赋值链路
最常见现象是 ctx.Value(key) 返回 nil,不是 API 写错了,而是 key 冲突或 ctx 没被真正替换下去。
- key 必须用未导出私有类型(如
type userIDKey struct{}),不能用字符串或 int —— 否则不同包里同名 key 会互相覆盖 -
context.WithValue返回的是新 context,原变量不变;必须显式赋值并传给下游函数,例如:ctx = context.WithValue(ctx, key, val)后,要确保nextFunc(ctx)用的是这个新ctx - HTTP handler 中,
r.Context()是只读副本,需手动r = r.WithContext(newCtx)才能让后续中间件拿到新值 -
ctx.Value只接受可比较类型(string、int、空 struct 等),传[]byte或map会在运行时 panic
WithCancel 和 WithTimeout 的 cancel 函数不能乱调
两者都返回 cancel 函数,但行为差异直接影响资源是否泄漏。
-
context.WithCancel(parent):取消完全由调用方控制;cancel()执行后再次调用会 panic(除非你加了防护) -
context.WithTimeout(parent, d):等价于WithDeadline(parent, time.Now().Add(d));超时后内部自动触发cancel(),且该cancel()是幂等的,可安全多次调用 - 测试/开发时最容易漏掉
defer cancel()—— 尤其在 goroutine 中创建了WithTimeout,不 defer 会导致*time.Timer无法被 GC,goroutine 泄漏
监听 ctx.Done() 不能只 select,必须查 ctx.Err()
仅写 case 会丢失取消原因,下游无法区分是用户主动取消还是超时,也容易忽略已关闭 channel 的后续清理逻辑。
- 正确姿势是在
case 分支里立即调用 <code>err := ctx.Err() - 用
errors.Is(err, context.Canceled)判断是否主动取消 - 用
errors.Is(err, context.DeadlineExceeded)判断是否超时 - 不同错误类型对应不同清理动作:比如超时可能要上报监控,而主动取消可能只需释放本地缓存
HTTP handler 中 context 传递最容易断在哪
从 http.ServeHTTP 开始,context 是只读副本,每层中间件或 handler 都必须显式绑定新 context 才能向下传递。
- 中间件中修改 context 后,必须返回新 request:
next.ServeHTTP(w, r.WithContext(newCtx)) - 不要把 context 存进 struct 字段 —— 违反 Go 官方规范,且会导致生命周期混乱、并发读写风险
- 别用
context.Background()替代请求级 context;入口处应统一用req.Context()起始派生 - database/sql、net/http/client 等标准库都支持 context,但必须显式传入,比如
db.QueryContext(ctx, ...),漏传就失去超时控制能力
真正的难点不在创建 context,而在每一层函数签名、每一次 goroutine 启动、每一个外部调用点,都得主动检查、更新、传递、监听——少一个环节,整条链就失效。这不是语法问题,是协作契约。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











