在 Go(Golang)中,context 是一个标准库提供的重要机制,用于控制协程(goroutine)之间的取消、超时、截止时间传递,以及上下文数据传递。它是并发编程中管理协程生命周期和避免资源泄漏的核心工具之一。

一、context 的主要用途

  1. 取消协程(Cancellation)
  2. 设置超时时间或截止时间(Timeout / Deadline)
  3. 跨 API 传递请求范围的数据(如认证信息、trace id)
  4. 防止资源泄露(确保任务完成或及时退出)

二、context 的基本接口和实现

接口定义(简化)

type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key any) any
}

四个常用的 context 构造函数

函数说明
context.Background()最基础的 context,通常用于 main 函数、初始化或测试中
context.TODO()表示目前还不确定要用哪个 context(类似占位符)
context.WithCancel(parent)返回一个新的 context 和一个取消函数,调用该函数会通知所有衍生的协程退出
context.WithTimeout(parent, timeout)指定超时自动取消
context.WithDeadline(parent, time.Time)指定某一时刻自动取消
context.WithValue(parent, key, val)传递 request-scoped 数据

三、使用示例

1. WithCancel 示例:手动取消任务

ctx, cancel := context.WithCancel(context.Background())

go func(ctx context.Context) {
    select {
    case <-ctx.Done():
        fmt.Println("goroutine exit:", ctx.Err())
    }
}(ctx)

time.Sleep(2 * time.Second)
cancel() // 手动取消

2. WithTimeout 示例:自动超时取消

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

select {
case <-time.After(3 * time.Second):
    fmt.Println("operation done")
case <-ctx.Done():
    fmt.Println("timeout:", ctx.Err()) // context deadline exceeded
}

3. WithValue 示例:传递请求数据

type key string
ctx := context.WithValue(context.Background(), key("userID"), 42)

func(ctx context.Context) {
    val := ctx.Value(key("userID"))
    fmt.Println("userID:", val)
}(ctx)

⚠️ 建议使用自定义类型作为 key,防止冲突。

四、协程泄露的场景与 context 的意义

很多时候,我们会在协程中执行耗时任务,如果外部请求被取消或超时,协程仍然运行,就会导致资源泄露。

通过 contextDone() 通道可以通知协程“取消”:

func doWork(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            fmt.Println("work canceled")
            return
        default:
            // 做一些事情
        }
    }
}

五、实践中常见的用法

  • HTTP 请求生命周期管理(Go 标准库中的 http.Request 已经自带 context)
  • gRPC 调用(Context 是核心机制)
  • 数据库查询、Redis、消息队列等外部服务调用
  • 微服务链路追踪中传递 trace ID、user token 等

六、注意事项和最佳实践

  1. 尽量用 context 控制 goroutine 生命周期
  2. 不要滥用 WithValue,它不是万能数据容器
  3. 及时调用 cancel(),避免资源泄漏
  4. 避免在多个 goroutine 中共享一个可变 context(除非只读)
  5. 每层调用传入 context,形成清晰调用链

七、Go 标准库中 context 的使用案例

  • http.Request.WithContext(ctx):用于绑定 request 生命周期
  • sql.DB.QueryContext(ctx):执行 SQL 时可以中途取消
  • grpc.NewServer(...opts...):服务端方法自动接收 context
  • exec.CommandContext(ctx, ...):运行外部命令并能中断

总结

特性描述
可取消协程间传递取消信号
超时控制自动取消长时间运行的任务
数据传递用于 request-scoped 数据
层级结构子 context 会随着父 context 的取消而取消
通用接口适用于标准库和第三方库中各种 IO、RPC、任务控制场景