go 的 context 包专用于请求级生命周期管理与跨 goroutine 协同取消;必须传 context 的三种场景是:涉及 i/o 等待、启动子 goroutine、参与请求链路。

Go 的 context 包不是万能的“超时控制工具”,而是为请求级生命周期管理和跨 goroutine 协同取消而生的核心机制。用错场景(比如给单个纯计算函数加 context)反而增加复杂度;用对地方(如 HTTP handler、数据库查询、长链路 RPC 调用),才能真正实现资源及时释放与错误快速传播。
何时必须传 context?看三个典型信号
遇到以下任一情况,就该把 context.Context 作为第一个参数显式传入函数:
- 涉及 I/O 等待:HTTP 请求、数据库查询、文件读写、网络调用 —— 这些操作天然可能阻塞,需支持主动中断
- 启动子 goroutine:只要 spawn 了新协程,就必须传递 context 并监听其 Done(),否则父请求取消时子协程可能持续运行(goroutine 泄漏)
- 参与请求链路:哪怕只是日志打点、指标上报、中间件透传,也应接收 context,以便携带 trace ID、deadline、value 等上下文信息
别直接用 context.Background() 或 context.TODO()
这两个是兜底值,不是默认选项:
-
context.Background()仅用于主函数、初始化或测试中“顶层无父上下文”的场景,绝不该出现在业务逻辑函数签名里 -
context.TODO()是占位符,表示“这里本该有 context,但我还没想好怎么传”——上线前必须替换,IDE 通常会标黄警告 - 正确做法:从上游接收 context,必要时用
WithTimeout、WithCancel、WithValue衍生新 context,并在函数退出前确保 cancel 函数被调用(defer 最安全)
并发任务编排:用 WithCancel 控制一组 goroutine
当一个请求需并行发起多个子任务(如查用户 + 查订单 + 调第三方服务),且任一失败或超时就应终止其余任务时,context.WithCancel 是最自然的选择:
立即学习“go语言免费学习笔记(深入)”;
- 用
ctx, cancel := context.WithCancel(parentCtx)创建可取消子上下文 - 将
ctx传给所有子 goroutine,每个 goroutine 在入口处select { case - 主 goroutine 根据结果(成功/失败/超时)主动调用
cancel(),触发所有子 goroutine 退出 - 务必 defer cancel(),防止因 panic 或提前 return 导致 cancel 遗漏
Value 用法:只传请求级元数据,不传业务参数
context.WithValue 常被误用为“函数参数传递通道”,这是反模式:
- ✅ 合理用途:trace ID、user ID、request ID、locale、log level —— 这些属于“请求上下文”,各层中间件/工具函数需要但不修改
- ❌ 错误用途:订单 ID、商品价格、配置开关 —— 这些是业务逻辑必需输入,应作为函数显式参数,保证可测试性与可读性
- 小技巧:定义私有 key 类型(如
type ctxKey string),避免字符串 key 冲突;取值时务必判空,v := ctx.Value(myKey); if v != nil { ... }
context 的本质是“请求的生命线”,不是魔法开关。它不帮你做并发,但让你的并发可控;它不替代错误处理,但让错误传播有统一出口。写函数时多问一句:“这个操作会不会等?要不要随请求一起结束?”——答案是“会”或“要”,就该加 context。










