Go 语言中惯用的 context.Context 用法——跨 API 边界传播、取消、超时和截止时间、请求作用域的值、用于超出请求存活期的后台工作的 context.WithoutCancel。适用于设计跨层级的 context 传播、调试泄漏或未过期的 context、在 context.Background/TODO/WithoutCancel 之间选择、或在 context 中存储值。不适用于仅将 ctx 作为第一个参数接受的代码。
社区默认。 明确取代
samber/cc-skills-golang@golang-context技能的公司技能具有优先权。
Go context.Context 最佳实践
context.Context 是 Go 用于跨 API 边界和 goroutine 传播取消信号、截止时间和请求作用域值的机制。可以将其视为请求的“会话”——它将属于同一工作单元的每个操作联系在一起。
最佳实践总结
- 同一个 context 必须贯穿整个请求生命周期:HTTP 处理器 → 服务 → 数据库 → 外部 API
ctx必须是第一个参数,命名为ctx context.Context- 永远不要将 context 存储在结构体中——通过函数参数显式传递
- 永远不要传递
nilcontext——如果不确定,使用context.TODO() - 对于
WithCancel/WithTimeout/WithDeadline,必须在所有控制流路径上调用cancel(),除非 context 和取消函数的所有权被显式返回或转移 context.Background()只能在顶层使用(main、init、测试)- 使用
context.TODO()作为占位符,当你知道需要 context 但还没有时 - 永远不要在请求路径中间创建新的
context.Background() - Context 值的键必须是未导出的类型,以防止冲突
- Context 值只能携带请求作用域的元数据——绝不能是函数参数
- 使用
context.WithoutCancel(Go 1.21+)在生成必须比父请求存活更久的后台工作时
创建 Context
| 场景 | 使用 |
|---|---|
| 入口点(main、init、测试) | context.Background() |
| 函数需要 context 但调用者尚未提供 | context.TODO() |
| 在 HTTP 处理器内部 | r.Context() |
| 需要取消控制 | context.WithCancel(parentCtx) |
| 需要截止时间/超时 | context.WithTimeout(parentCtx, duration) |
Context 传播:核心原则
最重要的规则:将同一个 context 传播到整个调用链。正确传播时,取消父 context 会自动取消所有下游工作。
// ✗ 错误——创建新的 context,断开了链
func (s *OrderService) Create(ctx context.Context, order Order) error {
return s.db.ExecContext(context.Background(), "INSERT INTO orders ...", order.ID)
}
// ✓ 正确——传播调用者的 context
func (s *OrderService) Create(ctx context.Context, order Order) error {
return s.db.ExecContext(ctx, "INSERT INTO orders ...", order.ID)
}
深入探讨
-
取消、超时与截止时间——取消如何传播:
WithCancel用于手动取消,WithTimeout用于在持续时间后自动取消,WithDeadline用于绝对时间截止。在并发代码中监听(<-ctx.Done())的模式,AfterFunc回调,以及WithoutCancel用于必须比父请求存活更久的操作(例如审计日志)。 -
Context 值与跨服务追踪——安全的 context 值模式:未导出的键类型以防止命名空间冲突,何时使用 context 值(请求 ID、用户 ID)与函数参数。追踪上下文传播:OpenTelemetry 追踪头、用于日志聚合的关联 ID,以及跨服务边界编组/解组 context。
-
HTTP 服务器与服务调用中的 Context——HTTP 处理器 context:
r.Context()用于请求作用域的取消,中间件集成,以及传播到服务。HTTP 客户端模式:NewRequestWithContext,客户端超时,以及具有 context 感知的重试。数据库操作:始终使用*Context变体(QueryContext、ExecContext)以尊重截止时间。
交叉引用
- → 参见
samber/cc-skills-golang@golang-concurrency技能,了解使用 context 的 goroutine 取消模式 - → 参见
samber/cc-skills-golang@golang-database技能,了解 context 感知的数据库操作(QueryContext、ExecContext) - → 参见
samber/cc-skills-golang@golang-observability技能,了解使用 OpenTelemetry 的追踪上下文传播 - → 参见
samber/cc-skills-golang@golang-design-patterns技能,了解超时和弹性模式
使用 Linter 强制执行
许多 context 陷阱可以通过 linter 自动捕获:govet、staticcheck。→ 参见 samber/cc-skills-golang@golang-lint 技能了解配置和使用。






