Go并发模型与模式:context 上下文控制

发布于:2025-06-13 ⋅ 阅读:(18) ⋅ 点赞:(0)

在 Go 的并发编程中,goroutine 虽然轻量高效,但也容易“失控”——一旦启动就很难精确停止。为了控制并发协程的取消、超时、信号传递和生命周期管理,Go 官方引入了 context 包。


一、为什么需要 context?

假设一个请求需同时发起多个协程处理,如果客户端中断请求,你就需要及时关闭所有协程以节省资源。这就是 context 的典型应用场景:

  • • ✅ 控制 goroutine 生命周期
  • • ✅ 统一管理取消信号和超时
  • • ✅ 传递跨 API 层的数据

二、context 的基本使用

Go 提供四种常见 context 创建方式:

context.Background()      // 最初始的上下文,常用作根 context
context.TODO()            // 占位用,尚未确定如何传入 context 时使用
context.WithCancel(ctx)   // 可调用 cancel() 主动取消
context.WithTimeout(ctx, d)  // 指定超时时间
context.WithDeadline(ctx, t) // 指定截止时间

三、context.WithCancel 示例:主动取消

package main

import (
    "context"
    "fmt"
    "time"
)

func worker(ctx context.Context, name string) {
    for {
        select {
        case <-ctx.Done():
            fmt.Println(name, "被取消")
            return
        default:
            fmt.Println(name, "正在工作")
            time.Sleep(time.Second)
        }
    }
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())

    go worker(ctx, "worker1")
    go worker(ctx, "worker2")

    time.Sleep(3 * time.Second)
    cancel() // 通知所有携带该 context 的协程停止
    time.Sleep(1 * time.Second)
}

四、context.WithTimeout 示例:自动超时取消

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()

    select {
    case <-time.After(5 * time.Second):
        fmt.Println("任务完成")
    case <-ctx.Done():
        fmt.Println("超时取消:", ctx.Err())
    }
}

五、在 channel 模式中使用 context 控制 goroutine

结合 channel 和 worker pool:

func worker(ctx context.Context, jobs <-chan int, results chan<- int) {
    for {
        select {
        case <-ctx.Done():
            return
        case job, ok := <-jobs:
            if !ok {
                return
            }
            results <- job * 2
        }
    }
}

六、context 的值传递功能

context 还可以在多个函数间传递一些只读值(例如:请求 ID、用户 token 等):

ctx := context.WithValue(context.Background(), "trace_id", "abc123")
doSomething(ctx)

func doSomething(ctx context.Context) {
    traceID := ctx.Value("trace_id")
    fmt.Println("Trace ID:", traceID)
}

⚠️ 不推荐用它来传递业务参数,仅用于日志追踪、认证信息等不可变值


七、最佳实践与注意事项

建议 描述
✅ 统一根 context 每个服务入口处使用 context.Background() 或 context.TODO()
✅ 及时调用 cancel() 否则 goroutine 不会释放,造成泄露
✅ 在 goroutine 内监听 ctx.Done() 接收到取消信号后及时退出
⚠️ 避免 context.Value 滥用 只传递与请求作用域相关的元信息

八、小结

  • • context 是 Go 并发模型中重要的资源控制机制;
  • • 能有效解决 goroutine 泄漏、超时处理、取消信号问题;
  • • 与 goroutine + channel 搭配使用,可以构建健壮的并发系统。