关于 golang-context
面向 Go 开发者的 context.Context 实践指南,说明如何在请求链路中传播取消信号、期限和请求级数据。内容涵盖上下文创建、值的使用边界及 HTTP、数据库和跨服务场景。
主要功能
- 01贯穿请求链路传播 context
- 02选择 Background、TODO 与请求上下文
- 03管理取消函数和超时
- 04安全使用 context 值
- 05在 HTTP 与数据库调用中传递 context
适用场景
- 01设计支持取消的服务调用
- 02为请求设置期限并避免资源泄漏
- 03在服务间传递请求元数据
- 04排查 context 使用不当
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ samber/ cc-skills-golang --skill golang-context安装后在 Claude Code 中输入/golang-context确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
evals/evals.jsonreferences/cancellation.mdhttp-services.mdvalues-tracing.md详细介绍
社区默认规则。 明确声明取代
samber/cc-skills-golang@golang-contextskill 的公司 skill 优先。
Go context.Context 最佳实践
context.Context 是 Go 用于在 API 边界之间以及 goroutine 之间传递取消信号、截止时间和请求级值的机制。可以把它看作请求的“会话”——它将属于同一工作单元的所有操作关联起来。
最佳实践摘要
- 在整个请求生命周期中传递同一个 context:HTTP 处理程序 → 服务 → DB → 外部 API——任何新建 context 的环节,都会在客户端断开后继续工作。
- 将
ctx作为第一个参数,命名为ctx context.Context——固定的位置让人一眼就能识别支持 context 的 API,也是 linter 检查的内容。 - 通过函数参数传递 context,而不是将其存储在 struct 中——struct 的生命周期会超过填充它的请求,因此后续调用可能会复用已取消或属于其他请求的 context。
- 传递
context.TODO(),而不是nilcontext——nil会在首次调用Done()或Value()时触发 panic,而且错误发生的位置离传入它的调用方很远。 - 对
WithCancel/WithTimeout/WithDeadline,在所有控制流路径上调用cancel(),除非 context 和 cancel 函数的所有权被明确返回或转移——如果不调用cancel(),子 context 会一直附着在父 context 上,并且其计时器会持续占用资源,直到父 context 结束。 - 仅在顶层入口点(main、init、测试)创建
context.Background()。在调用链更深处——尤其是在请求处理中途——它会使工作脱离调用方的截止时间和取消信号,这就是下方所示的传播中断。 - 当需要 context 但暂时没有时,使用
context.TODO()作为占位符——这会标记出待后续修复的问题,而不是用看似有意为之的Background()将其掩盖。 - 将 context 值的键声明为未导出类型——如果使用普通
string键,两个使用"user"的包会在不报错的情况下相互覆盖。 - 只在 context 值中携带请求级元数据,绝不携带函数参数——通过
Value()获取的值会失去编译时类型检查,也不会出现在函数签名中。 - 使用
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 会自动取消所有下游工作。
// ✗ Bad — creates a new context, breaking the chain
func (s *OrderService) Create(ctx context.Context, order Order) error {
return s.db.ExecContext(context.Background(), "INSERT INTO orders ...", order.ID)
}
// ✓ Good — propagates the caller's 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。 -
上下文值与跨服务跟踪 — 安全的上下文值模式:使用未导出的键类型以防止命名空间冲突;了解何时使用上下文值(请求 ID、用户 ID),何时使用函数参数。跟踪上下文传播:OpenTelemetry 跟踪标头、用于日志聚合的关联 ID,以及跨服务边界编组和解组上下文。
-
HTTP 服务与服务调用中的上下文 — HTTP 处理程序上下文:使用
r.Context()实现请求范围内的取消、与中间件集成,并将上下文传播到服务。HTTP 客户端模式:NewRequestWithContext、客户端超时,以及支持上下文感知的重试。数据库操作:始终使用*Context变体(QueryContext、ExecContext)以遵守截止期限。
交叉引用
- → 参阅
samber/cc-skills-golang@golang-concurrency技能,了解使用上下文的 goroutine 取消模式 - → 参阅
samber/cc-skills-golang@golang-database技能,了解支持上下文感知的数据库操作(QueryContext、ExecContext) - → 参阅
samber/cc-skills-golang@golang-observability技能,了解使用 OpenTelemetry 传播跟踪上下文 - → 参阅
samber/cc-skills-golang@golang-design-patterns技能,了解超时与弹性模式
使用 Linter 强制检查
许多上下文陷阱都可由 Linter 自动捕获:govet、staticcheck。→ 参阅 samber/cc-skills-golang@golang-lint 技能,了解配置和用法。