返回
创建于
状态
公开
context.Context 是 Go 语言在 并发编程 场景下用来“传递请求范围内元数据与控制信号”的标准机制。
一句话概括:它把“取消信号、超时控制、请求追踪等跨 API 的上下文信息”用 链式 的方式往 goroutine 树中层层传递,从而让成百上千个协程协同地“知进退、懂时限、带身份”。
1 为什么需要 Context?
- 取消
- Web 请求被用户中断、RPC 端提前关闭、CLI 收到 Ctrl-C——需要及时终止后台 goroutine,释放连接和资源。
- 超时 / 截止时间
- 下游服务卡住时,必须在 SLA 之前自动退出,避免阻塞整个链路。
- 跨层携带数据
- 追踪 ID、认证信息、locale、日志字段等,需要跨包、跨 goroutine 传递又不想把它们塞进函数签名里乱飞。
- 层级关联
- 一个请求可能衍生几十、上百个子任务;父任务结束后,子任务必须一并收敛(propagation)。
Context 把这四个痛点一次性解决,而且是 语言级“约定”:Go 1.7 起官方库、gRPC、database/sql、http 等都强制在 API 里首参加 ctx context.Context。
2 基本用法速览
1func handler(w http.ResponseWriter, r *http.Request) {
2 ctx := r.Context() // ← HTTP 框架自动构造
3 dbQuery(ctx) // 往下游传递
4}
5
6func dbQuery(ctx context.Context) {
7 // 包 db/sql 原生支持 ctx
8 row := db.QueryRowContext(ctx, "SELECT ...")
9 ...
10}创建与派生
| 目的 | 调用 | 说明 |
|---|---|---|
| 根 ctx | context.Background() | 进程级永不取消;main 函数或测试起点 |
| 可取消 | context.WithCancel(parent) | 返回 (ctx, cancel) 手动 cancel() |
| 超时 | context.WithTimeout(parent, 2*time.Second) | 到时自动取消 |
| 截止时间 | context.WithDeadline(parent, time.Time) | 固定时间点 |
| 附加键值 | context.WithValue(parent, key, val) | 仅用于跨 API 少量必须信息,不要滥用 |
3 常见场景示例
3-1 手动取消(CLI/批处理)
1ctx, cancel := context.WithCancel(context.Background())
2go work(ctx)
3
4time.Sleep(5 * time.Second) // 运行 5 秒后中止
5cancel()3-2 超时控制(HTTP 客户端)
1ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
2defer cancel()
3
4req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
5resp, err := http.DefaultClient.Do(req) // 3 秒不返回就自动取消3-3 追踪 ID 传递
1type traceIDKey struct{}
2
3func handler(w http.ResponseWriter, r *http.Request) {
4 traceID := r.Header.Get("X-Trace-ID")
5 ctx := context.WithValue(r.Context(), traceIDKey{}, traceID)
6 callDownstream(ctx)
7}
8
9func callDownstream(ctx context.Context) {
10 id, _ := ctx.Value(traceIDKey{}).(string)
11 log.Printf("trace=%s calling service-B", id)
12}提示:自己定义
struct{}作为 key,可避免包间冲突。
4 与 goroutine 协作
1func watchFile(ctx context.Context, path string) error {
2 ticker := time.NewTicker(time.Second)
3 defer ticker.Stop()
4 for {
5 select {
6 case <-ctx.Done(): // 收到取消 / 超时
7 return ctx.Err()
8 case <-ticker.C:
9 // do something
10 }
11 }
12}ctx.Done()返回只读 chan;关闭时意味着 ctx 已取消。ctx.Err()区分两类:context.Canceled(显式 cancel)和context.DeadlineExceeded(超时)。
5 最佳实践
| 建议 | 解释 |
|---|---|
| 参数列表第一个 | func Foo(ctx context.Context, …);保持一致性 |
| 不要存大对象 | WithValue 只放 少量 请求级元数据;大对象请走显式参数 |
及时 cancel() | 尤其在 WithTimeout / WithCancel,避免 goroutine 泄漏 |
| 避免在结构体里持有 ctx | Context 应随请求生命周期结束而释放 |
| 库内部也返回 ctx | 让调用者决定何时取消、何时超时 |
6 常见误区
- 忘记调用
cancel()- 子协程依旧存活,占用资源。
- 在子 goroutine 派生父 ctx 后立刻
cancel()父- 会提前把子 goroutine 切断。
- 滥用
WithValue做全局变量袋子- 违背“少量、必要”的原则,调试困难、接口隐晦。
- 在多处派生不同超时,嵌套层次混乱
- 建议在入口统一定一个总 deadline,下游只继承不扩张。
7 一句话总结
context.Context= “携带取消/超时/元数据的并发控制器”
它向整棵 goroutine 树传播“何时该结束”和“我是谁”的信息——
让分布式调用在正确的时间、正确的身份、正确的范围内优雅收敛。