web框架中传context.context是为了确保超时、取消和元数据穿透调用链;http handler必须用r.context()而非context.background(),中间件注入值、数据库查询、http请求等均需基于此派生并传入ctx。

Web框架中传context.Context不是为了“有上下文”,而是为了确保超时、取消和元数据能穿透整个调用链——漏掉一层,就可能卡死 goroutine 或丢失 trace ID。
HTTP handler 必须用 r.Context(),不能新建
Go 标准库的 *http.Request 已自带生命周期绑定的 context,它会在客户端断开、超时或服务端主动关闭连接时自动 cancel。硬编码 context.Background() 或 context.WithTimeout(context.Background(), ...) 会切断这个信号链。
- 错误写法:
ctx := context.WithTimeout(context.Background(), 3*time.Second)—— 跟请求生命周期无关,客户端关掉浏览器也停不掉 - 正确写法:
ctx := r.Context(),再基于它派生:ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second) - 中间件注入值也必须基于
r.Context():ctx := context.WithValue(r.Context(), requestIDKey, id),否则下游取不到
QueryContext、Do 等阻塞方法必须显式传 ctx
数据库驱动(如 database/sql)、HTTP 客户端(如 http.Client.Do)、gRPC 客户端等主流库都提供 XXXContext 版本方法。不传 ctx,这些操作就对 cancel 和 timeout 完全无感。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
-
db.QueryRow("SELECT ...")→ 不响应取消;db.QueryRowContext(ctx, "SELECT ...")→ 取消时立即中断查询 -
http.DefaultClient.Do(req)→ 可能永远 hang;http.DefaultClient.Do(req.WithContext(ctx))→ 超时后自动 abort 连接 - 注意:不是所有第三方库都支持 context,遇到不支持的,得自己包装成可取消逻辑(比如用
select监听ctx.Done()+time.AfterFunc)
WithValue 只能存不可变元数据,且键类型必须自定义
context.WithValue 是唯一允许往 context 写数据的机制,但它不是函数参数替代品,更不是传递业务对象的通道。
- 允许存:
requestID、userID、traceID、lang—— 全是只读、轻量、请求生命周期内不变的字符串或基本类型 - 禁止存:
*sql.DB、http.Client、struct{...}—— 违反依赖注入原则,导致测试困难、内存泄漏风险 - 键必须是未导出类型:
type userIDKey struct{},而不是"user_id"字符串 —— 避免不同包之间 key 冲突 - 取值必须判空:
if v, ok := ctx.Value(userIDKey{}).(int); ok { ... },不能直接断言
WithTimeout 和 WithDeadline 的 cancel 函数必须 defer 调用
这两个函数返回的 cancel 函数不只是“取消”那么简单——它负责清理内部 timer、释放 goroutine 引用。漏掉 defer cancel(),会导致 timer 持续运行、goroutine 泄露。
- 常见错误:
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)后没defer cancel(),哪怕函数提前 return 也会泄露 -
WithTimeout适合“最多执行 N 秒”,WithDeadline适合“必须在某时刻前完成”(比如配合分布式锁续期) - 不要嵌套多次
WithTimeout:ctx1 := context.WithTimeout(ctx, 2*s),再ctx2 := context.WithTimeout(ctx1, 1*s)—— 子 context 的 deadline 是叠加的,行为难预测
最易被忽略的一点:context 不是线程安全的 Value 读写容器,WithValue 后的子 context 可以安全读,但多个 goroutine 并发写同一个 key 会导致 panic;所有写操作必须发生在派生时,且仅一次。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










