Skip to content

context

  • 是处理 goroutine 生命周期、请求范围元数据传递以及超时控制的核心工具。

解决痛点

  • 如何优雅终止子 goroutine:当主逻辑退出(如请求取消、超时),如何通知所有子 goroutine 停止工作并释放资源?
  • 如何传递请求范围的元数据:一个请求链路中(如 HTTP 请求),需要传递的超时时间、认证信息等,如何在多个 goroutine 间高效共享?
  • context 包的出现就是为了统一解决这些问题,提供了跨 goroutine 的上下文管理机制。

数据结构

go
type Context interface {
    // 返回上下文的截止时间(若存在)
    Deadline() (deadline time.Time, ok bool)
    // 返回一个通道,当上下文被取消或超时,该通道会关闭
    Done() <-chan struct{}
    // 返回上下文取消的原因(若已取消)
    Err() error
    // 获取上下文存储的键值对(用于传递元数据)
    Value(key interface{}) interface{}
}

Context类型

类型描述
context.Background()空上下文,通常用于根上下文
context.TODO()占位符上下文,用于临时代码或未确定的上下文
context.WithCancel(parent Context)创建一个可取消的上下文,当调用取消函数时,所有子上下文也会被取消
context.WithDeadline(parent Context, deadline time.Time)创建一个带截止时间的上下文,超过截止时间后,所有子上下文也会被取消
context.WithTimeout(parent Context, timeout time.Duration)创建一个带超时时间的上下文,超过超时时间后,所有子上下文也会被取消
context.WithValue(parent Context, key, value interface{})创建一个带键值对的上下文,用于在子上下文间传递元数据

信号传递与生命周期

  • 上下文的核心是「取消信号的传递」,其实现依赖于 Done() 方法返回的通道:
  • 当上下文被取消(手动调用取消函数、超时或到截止时间),Done() 通道会被关闭(关闭的通道可被读取,返回零值)。
  • 子 goroutine 通过监听 Done() 通道,即可感知上下文状态,进而终止工作。

父子关系特性

  • 父上下文被取消时,所有派生的子上下文会被递归取消(信号沿上下文树传递)。
  • 子上下文取消不会影响父上下文。

使用场景

  • 每个请求的 *http.Request 都包含一个 Context,可用于传递请求超时、认证信息,或在请求取消时终止后端处理(如数据库查询、RPC 调用)
go
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    // 从请求中获取上下文(客户端断开连接时会自动取消)
    ctx := r.Context()
    // 基于请求上下文创建 5 秒超时的子上下文
    subCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
    defer cancel()
    // 使用 subCtx 进行数据库查询等操作...
})

最佳实践

  • 上下文应作为函数的第一个参数:遵循 Go 社区约定,便于阅读和维护,如 func DoSomething(ctx context.Context, arg int)
  • 优先使用 context.Background() 作为根上下文:作为所有其他上下文的基础,确保上下文树的根节点是可取消的。
  • 不传递 nil 上下文:即使函数暂时不需要上下文,也应传递 context.Background()context.TODO()
  • 避免在上下文中存储大量数据:WithValue 适合轻量元数据,大量数据应通过参数或结构体传递。
  • 及时调用 cancel 函数:WithCancel/WithTimeout 返回的 cancel 函数必须调用(通常用 defer),否则会导致上下文泄漏(goroutine 可能一直阻塞)。
  • 不修改上下文的值:上下文值(通过 WithValue 创建)是只读的,不应该在子 goroutine 中被修改。