context.Background()仅用于main函数、独立goroutine和单元测试;context.TODO()仅作临时占位,上线前必须替换;HTTP handler中误用Background会导致请求生命周期失控。
context.Background() 该在哪儿用
它就是程序的“起点 context”,只适合用在没有父上下文可继承的场景:
- main() 函数开头,作为所有后续 context.withcancel、context.withtimeout 的根
- 启动一个完全独立于任何请求的 goroutine(比如定时健康检查、后台清理任务)时,用它派生子 context
- 单元测试里需要传入非 nil 的 context.context,又不想干扰逻辑,就直接用 context.background()
context.TODO() 是个临时标记,不是备选方案
context.TODO() 唯一合法用途是:你正在写代码,但还没决定该传哪个 context——此时填它,等于加了个 TODO 注释。
- 函数签名里有 ctx context.Context 参数,调用方暂时没提供,你不能假设它属于某个 HTTP 请求,就先写 context.TODO()
- 团队协作中,别人看到这个调用,会立刻意识到“这里要补上下文逻辑”
- 静态检查工具(如 staticcheck)默认把未替换的 context.TODO() 当作警告,上线前必须处理
误用 background 导致请求生命周期失控
最常见也最危险的错误,是在 HTTP handler 内部直接用 context.Background() 启动子任务:
- 比如在 http.HandlerFunc 里调用 doWork(context.Background())
- 这会导致子任务完全脱离当前请求的取消信号:用户关掉页面、客户端断连、超时触发,它还在跑
- 正确做法是用 req.Context() 派生,例如 ctx, cancel := context.WithTimeout(req.Context(), 5*time.Second)
- 同理,数据库查询、RPC 调用、消息发送等所有下游操作,都应继承请求上下文,而不是另起炉灶
emptyCtx 不是问题,上下文树断裂才是
context.Background() 和 context.TODO() 返回的都是 emptyCtx,不可取消、无截止时间、不带值——这点完全一致,运行时毫无区别。
- 真正关键的是:每个 goroutine 是否从一个有意义的父 context 派生
- 即使只是加个 traceID:ctx = context.WithValue(parent, traceKey, "abc123"),也比裸用 emptyCtx 强
- 把 TODO() 留在线上,等于主动放弃可观测性;把 Background() 乱塞进 handler,等于切断传播链


















