返回
创建于
状态公开

context.Context 是 Go 语言在 并发编程 场景下用来“传递请求范围内元数据与控制信号”的标准机制。
一句话概括:它把“取消信号、超时控制、请求追踪等跨 API 的上下文信息”用 链式 的方式往 goroutine 树中层层传递,从而让成百上千个协程协同地“知进退、懂时限、带身份”。


1 为什么需要 Context

  1. 取消
    • Web 请求被用户中断、RPC 端提前关闭、CLI 收到 Ctrl-C——需要及时终止后台 goroutine,释放连接和资源。
  2. 超时 / 截止时间
    • 下游服务卡住时,必须在 SLA 之前自动退出,避免阻塞整个链路。
  3. 跨层携带数据
    • 追踪 ID、认证信息、locale、日志字段等,需要跨包、跨 goroutine 传递又不想把它们塞进函数签名里乱飞。
  4. 层级关联
    • 一个请求可能衍生几十、上百个子任务;父任务结束后,子任务必须一并收敛(propagation)。

Context 把这四个痛点一次性解决,而且是 语言级“约定”:Go 1.7 起官方库、gRPC、database/sql、http 等都强制在 API 里首参加 ctx context.Context


2 基本用法速览

go
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}

创建与派生

目的调用说明
根 ctxcontext.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/批处理)

go
1ctx, cancel := context.WithCancel(context.Background())
2go work(ctx)
3
4time.Sleep(5 * time.Second) // 运行 5 秒后中止
5cancel()

3-2 超时控制(HTTP 客户端)

go
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 传递

go
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 协作

go
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 泄漏
避免在结构体里持有 ctxContext 应随请求生命周期结束而释放
库内部也返回 ctx让调用者决定何时取消、何时超时

6 常见误区

  1. 忘记调用 cancel()
    • 子协程依旧存活,占用资源。
  2. 在子 goroutine 派生父 ctx 后立刻 cancel()
    • 会提前把子 goroutine 切断。
  3. 滥用 WithValue 做全局变量袋子
    • 违背“少量、必要”的原则,调试困难、接口隐晦。
  4. 在多处派生不同超时,嵌套层次混乱
    • 建议在入口统一定一个总 deadline,下游只继承不扩张。

7 一句话总结

context.Context = “携带取消/超时/元数据的并发控制器”
它向整棵 goroutine 树传播“何时该结束”和“我是谁”的信息——
让分布式调用在正确的时间、正确的身份、正确的范围内优雅收敛。