Go: 标准库 context
传播链的生命周期,跨边界的上下文
context 包定义了一个上下文Context类型:这个类型在跨越 API 边界和进程之间,携带了超时,取消信号,以及其他请求域内的值;
本质:一条调用链/传播链上各个节点共享一套:是否超时、是否取消、值的机制。
Context 接口
type Context interface {
// 返回截止时间,ok 为 false 表示没有设置 deadline
// ! 未来什么时候结束,还未结束
Deadline() (deadline time.Time, ok bool)
// 返回一个 channel,当 context 被取消或超时时关闭;永远不会取消的 context 返回 nil
// ! 已经结束
Done() <-chan struct{}
// Done 未关闭,返回 nil;已关闭返回原因(Canceled 或 DeadlineExceeded)
Err() error
// 返回 key 关联的值,不存在返回 nil
Value(key any) any
}context 是接受信号的一方进行协同操作。
派生树模型
派生树模型,在父ctx上派生出子ctx
当一个 Context 被取消时,所有从它派生的子 Context 也会被取消
根 Context
context.Background()— 通常用于 main、init 或顶层请求入口context.TODO()— 不确定用哪个 Context 时的占位符
派生函数
| 函数 | 作用 |
|---|---|
WithCancel(parent) |
返回可手动取消的子 context 和 cancel 函数 |
WithDeadline(parent, d) |
到达时间点 d 自动取消 |
WithTimeout(parent, t) |
经过时长 t 自动取消(等价于 WithDeadline(parent, time.Now().Add(t))) |
WithValue(parent, key, val) |
附带一个 key-value 对,用于请求域数据的传递 |
WithoutCancel(parent) 返回一个不会随父 context 取消而取消的派生 context
AfterFunc(ctx, f) ctx 取消后,在新 goroutine 中执行 f(Go 1.21)
带 Cause 的 API: context 被取消时,通过Cause获取具体原因
| 函数 | 作用 |
|---|---|
WithCancelCause(parent) |
在普通取消基础上额外记录一个自定义 error 原因(Go 1.20+) |
WithDeadlineCause(parent, d, cause) |
在 deadline 取消基础上额外记录一个自定义 error 原因 |
WithTimeoutCause(parent, t, cause) |
在 timeout 取消基础上额外记录一个自定义 error 原因 |
Cause(ctx) |
读取 context 被取消的具体原因;若未显式指定 cause,则返回值通常与 ctx.Err() 一致 |
错误值
调用 Err() 返回
context.Canceled— 手动调用 cancel 函数时context.DeadlineExceeded— deadline/timeout 到期时
取消的原因
谁先 cancel,谁定义原因cause
NOTE: 为什么 WithTimeoutCause/WithDeadlineCause 不返回 CancelCauseFunc:因为手动 cancel → 原因变成 context.Canceled
使用规则
- 不要把 Context 存在 struct 里,显式作为函数第一个参数传递,通常命名
ctx - 不要传 nil Context,不确定时用
context.TODO() - Value 只用于请求域数据(如 trace ID、认证信息),不要用来传可选参数
- Context 是并发安全的,多个 goroutine 可同时使用
cancel函数调用后要尽快执行(通常defer cancel()),避免资源泄漏
Go 中 context.Value 应该放什么
如果你的代码没有这个值就无法运行,那这个值就不应该放进 context。
context 是附带信息的通道,不是参数传递的替代品
评论