Context必须从HTTP请求入口(如handler)创建并显式传递,优先用r.Context()而非context.Background();自定义值需用私有类型key避免冲突;DB操作必须用带Context后缀方法,否则超时取消失效。

Context传递必须在HTTP请求入口就创建
Go的context.Context不是全局变量,也不能在任意位置临时生成后“塞进”调用链。它必须从请求入口(如HTTP handler)开始显式传入,否则下游无法感知超时、取消或携带值。常见错误是:在某个业务函数里自己调用context.WithTimeout,结果上层根本收不到取消信号,导致goroutine泄漏。
正确做法是让handler成为唯一创建点:
func handler(w http.ResponseWriter, r *http.Request) {
// 从request中提取基础context(含cancel、timeout等)
ctx := r.Context()
<pre class="brush:php;toolbar:false;">// 可叠加额外控制,比如限制本请求内DB操作最多500ms
ctx, cancel := context.WithTimeout(ctx, 500*time.Millisecond)
defer cancel()
err := doSomething(ctx)
// ...}
- 永远优先用
r.Context()而非context.Background(),前者已集成HTTP生命周期管理 - 如果需要传递自定义值(如用户ID),用
context.WithValue,但key必须是私有类型,避免冲突 - 不要把
context.Context作为结构体字段长期保存——它只应随一次请求流转
WithValue的key不能用string或int直传
用context.WithValue(ctx, "user_id", 123)看似简单,但极易引发key冲突或类型断言失败。Go官方文档明确要求key必须是未导出的自定义类型,确保唯一性和类型安全。
立即学习“go语言免费学习笔记(深入)”;
典型错误写法会触发panic: interface conversion: interface {} is int, not string,尤其在中间件多层嵌套时。
- 定义key:
type ctxKey string,然后声明const userKey ctxKey = "user" - 存值:
ctx = context.WithValue(ctx, userKey, userID) - 取值:
if id, ok := ctx.Value(userKey).(int64); ok { ... } - 切勿在多个包中复用同一字符串key;哪怕看起来“不会撞”,运行时也无法检查
数据库查询必须接收context参数
标准库database/sql和主流驱动(如pgx、mysql)都支持带context的查询方法,例如db.QueryRowContext、tx.StmtContext。不使用这些方法,context的超时和取消就完全失效——SQL可能卡死,而HTTP handler早已返回或超时。
错误示例:row := db.QueryRow("SELECT ...") —— 这个调用完全忽略context,也不响应cancel。
- 所有
Query/Exec/QueryRow的变体,只要名字带Context后缀,就必须传入有效ctx - 事务中更要小心:用
tx.QueryRowContext而非db.QueryRowContext,否则可能跨事务泄露 - 连接池等待也受context控制——若context已取消,驱动不会去拿空闲连接,直接报
context canceled
第三方SDK不支持context时得手动包装
有些老SDK(比如某些云厂商的Go SDK)只提供无context的同步接口,这时不能硬加context.WithTimeout然后扔给go func(){...}()了事。那样只是启动协程,没解决阻塞问题,且容易漏defer cancel。
真正可行的是用select + channel做超时兜底:
resultCh := make(chan Result, 1)
go func() {
resultCh <- legacySDK.DoSomething()
}()
<p>select {
case res := <-resultCh:
handle(res)
case <-ctx.Done():
return ctx.Err() // 或记录日志、清理资源
}- 注意
resultCh要带缓冲,避免goroutine永久阻塞在发送上 - 如果SDK本身有回调或异步模式,优先改用其原生异步接口,再对接context
- 别忘了在
ctx.Done()分支里主动中断SDK可能持有的长连接(如有Close方法)
context不是魔法,它只是一条贯穿请求的信号线。传错一层、漏一个Context参数、或key类型不严谨,整条链就断了——而这种问题往往在线上压测或网络抖动时才暴露。


















