正确做法是:从http.request.context()派生新context并传给业务函数;勿存struct字段长期持有;withvalue仅用于元数据且key须为自定义类型;goroutine、db/redis调用等必须显式传ctx,漏任一环则超时取消失效。

Context在HTTP handler中怎么正确传递
不手动传参就丢上下文,context.WithValue 不能乱塞,否则下游拿不到。HTTP handler 是最常见也是最容易出错的起点。
标准写法是:从 http.Request.Context() 派生新 context,再通过参数传给业务函数;不要把 context 存到结构体字段里长期持有(除非明确生命周期可控)。
- 错误示范:
r.Context()直接存进 struct,后续异步调用时可能已 cancel - 正确做法:每个 handler 内部用
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second),然后立刻 defercancel() - 若需携带请求元信息(如 traceID),用
context.WithValue(ctx, key, value),但 key 必须是自定义类型(避免字符串冲突),例如type ctxKey string; const TraceIDKey ctxKey = "trace_id"
goroutine 启动时 Context 怎么带进去
直接传 context.Background() 或硬编码 context.TODO() 就等于放弃链路控制。goroutine 生命周期不可控,必须显式继承父 context。
典型陷阱是:用 go fn() 启动协程,但 fn 内部没接收 context 参数,或接收了却没传给下游调用。
- 启动前必须派生:用
ctx := context.WithTimeout(parentCtx, time.Second*3),再go fn(ctx, ...) - 如果 fn 内部还要起子 goroutine,同样要继续传 ctx,不能“断链”
- 注意
time.AfterFunc、time.Tick这类定时器不会自动感知 cancel,得自己监听ctx.Done()并退出
数据库/Redis客户端是否支持Context
不支持 context 的 client 库会阻塞整个链路超时控制。Go 生态主流驱动基本都已支持,但调用方式容易被忽略。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
比如 database/sql 的 QueryContext、ExecContext 替代旧版 Query/Exec;redis-go 的 Get(ctx, key) 而非 Get(key)。
- 检查方法:看 client 方法签名有没有
context.Context第一个参数 - 常见漏点:事务内嵌套查询时,仍用
tx.Query而非tx.QueryContext - 第三方库如
ent、gorm都提供WithContext(ctx)链式调用,别跳过
Context.Value 传参和性能影响怎么看
context.WithValue 不是通用传参工具,它本质是只读 map + interface{},高频存取会触发反射和内存分配。
只适合传少量、低频、跨层共享的元数据(如用户 ID、traceID、tenantID),绝不该用来传业务参数或结构体指针。
- 性能敏感路径(如日志中间件、metrics 打点)避免反复
ctx.Value(key),可提前解包缓存到局部变量 - 多个值要用不同 key 类型,别用
string做 key(易冲突),例如var UserIDKey = struct{}{} - 一旦用了
WithValue,下游必须做if v, ok := ctx.Value(key).(type)类型断言,不能假设一定存在
链路传递真正难的不是语法,而是所有中间件、client、goroutine、回调函数都得统一接入——漏掉任意一环,timeout 和 cancel 就会失效。最常被跳过的其实是日志打点和 metrics 上报里的 context 检查。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










