Go context 包详解

Go context 包详解

一句话总结

context 是 Go 并发编程的核心工具,用于在 goroutine 之间传递截止时间、取消信号和请求级元数据


为什么需要 context?

复制代码
HTTP 请求 → 启动多个 goroutine(查DB、调RPC、查缓存)
                ↓
请求被客户端取消 → 所有子 goroutine 必须立即停止,释放资源

没有 context,你只能手动传 done chan struct{},代码会变得很丑且容易泄漏 goroutine。


四种创建方式

1. Background() --- 根上下文

go 复制代码
// 最顶层使用,永不取消,永不超时
ctx := context.Background()

2. TODO() --- 占位用

go 复制代码
// 还不确定用啥时先用 TODO,后续替换
ctx := context.TODO()

3. WithCancel --- 手动取消

go 复制代码
ctx, cancel := context.WithCancel(parentCtx)

// 某个条件触发时调用
cancel() // 向所有监听 ctx.Done() 的 goroutine 发送取消信号

典型场景:用户主动取消请求、前置条件不满足时中断下游。

4. WithTimeout --- 超时取消

go 复制代码
// 3秒后自动取消(内部也是 WithCancel + timer)
ctx, cancel := context.WithTimeout(parentCtx, 3*time.Second)
defer cancel() // 养成好习惯,避免泄漏

5. WithDeadline --- 绝对时间取消

go 复制代码
// 到指定时间点自动取消
ctx, cancel := context.WithDeadline(parentCtx, time.Now().Add(5*time.Minute))
defer cancel()

6. WithValue --- 传递请求级元数据

go 复制代码
type contextKey string
const traceIDKey contextKey = "traceID"

ctx := context.WithValue(parentCtx, traceIDKey, "abc-123")
val := ctx.Value(traceIDKey) // "abc-123"

核心 API

方法 返回值 作用
ctx.Done() <-chan struct{} 取消时关闭,用于 select 监听
ctx.Err() error 返回 CanceledDeadlineExceeded
ctx.Value(key) any 读取上下文值
ctx.Deadline() (time.Time, bool) 返回截止时间

实战示例

示例 1:超时控制

go 复制代码
func fetchWithTimeout(url string) ([]byte, error) {
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()

    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return nil, err
    }

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, err // 超时会返回 context.DeadlineExceeded
    }
    defer resp.Body.Close()

    return io.ReadAll(resp.Body)
}

示例 2:链式取消(扇出)

go 复制代码
func processOrder(ctx context.Context, orderID string) error {
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()

    var wg sync.WaitGroup
    errCh := make(chan error, 3)

    // 并行调用三个服务
    services := []string{"inventory", "payment", "notification"}
    for _, svc := range services {
        wg.Add(1)
        go func(name string) {
            defer wg.Done()
            if err := callService(ctx, name, orderID); err != nil {
                errCh <- fmt.Errorf("%s failed: %w", name, err)
                cancel() // 一个失败,全部取消
            }
        }(svc)
    }

    go func() {
        wg.Wait()
        close(errCh)
    }()

    for err := range errCh {
        return err // 返回第一个错误
    }
    return nil
}

示例 3:goroutine 正确退出模式

go 复制代码
func worker(ctx context.Context, ch <-chan int) {
    for {
        select {
        case <-ctx.Done():
            // 收到取消信号,清理并退出
            fmt.Println("worker exiting:", ctx.Err())
            return
        case val, ok := <-ch:
            if !ok {
                return // channel 关闭
            }
            process(val)
        }
    }
}

上下文传播链(重要!)

复制代码
Background
  └─ WithTimeout(5s)       → ctx1
       └─ WithCancel()     → ctx2
            └─ WithValue() → ctx3

关键规则

  • 子 context 取消 → 父 context 不受影响
  • 父 context 取消 → 所有子 context 级联取消 (这就是 WithCancel 传 parent 的原因)
  • 形成树状结构,非常适合 HTTP 请求的生命周期管理

最佳实践

✅ DO

go 复制代码
// 1. 函数第一个参数放 context,命名为 ctx
func DoSomething(ctx context.Context, arg string) error { ... }

// 2. 不要把 context 存在结构体里,通过参数传递
// 3. 用自定义类型做 key,避免冲突
type myKey string
ctx := context.WithValue(parent, myKey("userID"), 42)

// 4. defer cancel(),防止泄漏
ctx, cancel := context.WithTimeout(parent, timeout)
defer cancel()

❌ DON'T

go 复制代码
// 1. 不要传 nil context
DoSomething(nil, "bad") // 应该用 context.Background()

// 2. 不要用 context.Value 传业务参数
ctx := context.WithValue(parent, "userID", 42) // string key 容易冲突

// 3. 不要把 context 放进结构体
type Service struct {
    ctx context.Context // ❌ 应通过方法参数传递
}

context 的性能考量

操作 耗时 说明
WithCancel ~30ns 极轻量,只是创建结构体
WithValue ~50ns 轻量,用链表存储
ctx.Done() ~5ns 只是读 channel
ctx.Value() O(n) 沿链表查找,n = value 层数

结论 :正常使用基本不需要担心性能。唯一要注意的是 Value() 不要嵌套太深。


一张图总结

复制代码
           context.Background()
                   │
          ┌────────┼────────┐
     WithTimeout  WithCancel  WithValue
         │           │           │
    ┌────┘      ┌────┘      ┌────┘
  子goroutine  子goroutine  子goroutine
    
    cancel() 或 超时到期 或 Deadline 到达
         ↓
    ctx.Done() 关闭 → 所有 select 监听收到信号 → goroutine 退出

核心思想就一句话:context 是 goroutine 的生命控制器

相关推荐
隐士Xbox1 小时前
c++ 指针的用法
开发语言·c++·计算机视觉
江南十四行1 小时前
Python元类编程——从type到metaclass的深度探索
开发语言·python
众乐乐_20082 小时前
PHP 的进程 fork 机制
开发语言·php
yujunl2 小时前
U9 WCF调试的一个坑
开发语言
lly2024062 小时前
Scala 模式匹配
开发语言
2zcode2 小时前
基于MATLAB卷积神经网络的多颜色车牌识别系统设计与实现
开发语言·matlab·cnn
无限进步_2 小时前
【C++】从红黑树到 map 和 set:封装设计与迭代器实现
开发语言·数据结构·数据库·c++·windows·github·visual studio
Hello eveybody2 小时前
介绍一下动态树LCT(Python)
开发语言·python·算法
handler012 小时前
速通蓝桥杯省一:二分算法
c语言·开发语言·c++·笔记·算法·职场和发展·蓝桥杯