
context
context
用于在 goroutine 间传递特定数据、取消信号等上下文信息。一般用于服务器中的请求链路中,实际应用示例不在本文赘述。
本文基于 Go 1.24.5 的源码,从实现角度逐层分析 context
的结构设计与取消机制。
context 接口定义
Context
是一个抽象接口,定义了取消、超时和键值传递的统一语义,所有具体类型(如 cancelCtx
、timerCtx
)都是它的实现。
Context interface ├── emptyCtx │ ├── backgroundCtx │ └── todoCtx ├── cancelCtx │ └── timerCtx └── valueCtx
type Context interface { // Deadline 返回 context 被取消的时间 // 返回 false 代表无 deadline,不会自动取消 Deadline() (deadline time.Time, ok bool)
// Done 返回一个 channel,当 context 被取消,即工作完成时,会关闭这个 channel // 若 context 不会被取消,Done 返回 nil // Done 关闭可能会与 cancel 函数的返回异步发生 Done() <-chan struct{}
// 在 Done 被关闭后,返回 context 的取消原因 Err() error
// Value 返回 context 中对应键中存储的值,若键不存在则返回 nil Value(key any) any}
emptyCtx: TODO/Background
emptyCtx
是最简单的 context
接口实现,永不取消,不存储键值对,无 deadline。
// backgroundCtx 与 todoCtx 在实现上都是 emptyCtx,仅有 String() 一处不同type emptyCtx struct{}
func (emptyCtx) Deadline() (deadline time.Time, ok bool) { return}
func (emptyCtx) Done() <-chan struct{} { return nil}
func (emptyCtx) Err() error { return nil}
func (emptyCtx) Value(key any) any { return nil}
type backgroundCtx struct{ emptyCtx }
func (backgroundCtx) String() string { return "context.Background"}
type todoCtx struct{ emptyCtx }
func (todoCtx) String() string { return "context.TODO"}
// Background 用于创建最顶层 contextfunc Background() Context { return backgroundCtx{}}
// TODO 用于不确定需要使用何种 context 时作为占位符func TODO() Context { return todoCtx{}}
WithCancel
WithCancel
是 context
的核心构造函数之一,它返回一个可手动取消的 context
。其底层依赖 cancelCtx
来实现取消传播。cancelCtx
是带 cancel 功能的 context
,在调用 cancel()
时也会同时取消其子 context
。
cancelCtx
cancelCtx
中嵌套了一个 Context
的匿名字段,这个字段代表其父节点。同时,cancelCtx
本身也实现了Context
接口。
type cancelCtx struct { Context // 嵌入其父 ctx mu sync.Mutex // 互斥锁,保证线程安全 done atomic.Value // 存储一个 chan struct{}, 懒加载,在调用 cancel() 时关闭 children map[canceler]struct{} // 子 ctx 的集合,在 cancel() 被调用时设为 nil err error // 在 cancel() 被调用时设置 cause error // 取消原因}
// cancelCtxKey 是一个 int 指针类型的全局变量,用于判断当前 ctx 是否为 cancelCtx类型// 返回最早的 cancelCtxfunc (c *cancelCtx) Value(key any) any { // 返回自身 if key == &cancelCtxKey { return c } return value(c.Context, key)}
// 当找不到 &cancelCtxKey 时会继续调用 value(c.Context, key) 向上寻找func value(c Context, key any) any { for { switch ctx := c.(type) { case *valueCtx: if key == ctx.key { return ctx.val } c = ctx.Context case *cancelCtx: if key == &cancelCtxKey { return c } c = ctx.Context case withoutCancelCtx: if key == &cancelCtxKey { return nil } c = ctx.c case *timerCtx: if key == &cancelCtxKey { return &ctx.cancelCtx } c = ctx.Context case backgroundCtx, todoCtx: return nil default: return c.Value(key) } }}
// 懒创建并返回 donefunc (c *cancelCtx) Done() <-chan struct{} { d := c.done.Load() if d != nil { return d.(chan struct{}) } c.mu.Lock() defer c.mu.Unlock() d = c.done.Load() if d == nil { d = make(chan struct{}) c.done.Store(d) } return d.(chan struct{})}
func (c *cancelCtx) Err() error { c.mu.Lock() err := c.err c.mu.Unlock() return err}
cancelCtx
不仅实现了 Context
,同时还实现了 canceler
接口,代表可取消的 ctx:
// cancelCtx 和 timerCtx 都实现了 canceler 接口type canceler interface { cancel(removeFromParent bool, err, cause error) Done() <-chan struct{}}
在 children 中存储的就是当前 cancelCtx
的最近的子 cancelCtx
在创建 cancelCtx
的同时,定义 cancel
函数,代码如下:
// WithCancel 返回一个带有新 Done channel 的派生 context// 当父 context 被取消或当前 context 被取消时,Done 都会被关闭func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { c := withCancel(parent) return c, func() { c.cancel(true, Canceled, nil) }}
func withCancel(parent Context) *cancelCtx { if parent == nil { panic("cannot create context from nil parent") } c := &cancelCtx{} c.propagateCancel(parent, c) // 建立父子关系 return c}
cancel()
cancel()
会关闭自身的 done
,设置 cause
,递归通知所有子 ctx,然后将当前的 ctx 从父节点中移除。
// 关闭 c.done,取消 c 的所有 children// 若 removeFromParent 为 true, 把 c 从它父节点的 children 中移除func (c *cancelCtx) cancel(removeFromParent bool, err, cause error) { if err == nil { panic("context: internal error: missing cancel error") } if cause == nil { cause = err // cause 未设置,取 err } c.mu.Lock() if c.err != nil { c.mu.Unlock() return // context 已取消 } c.err = err c.cause = cause d, _ := c.done.Load().(chan struct{}) if d == nil { c.done.Store(closedchan) // 存 closedchan 作为已取消的标志 } else { close(d) // 如果 done 已被设置,直接关闭 d } // 递归取消所有的子 context for child := range c.children { child.cancel(false, err, cause) } c.children = nil c.mu.Unlock()
if removeFromParent { // 移除当前子 context removeChild(c.Context, c) }}
removeChild()
removeChild()
的作用是寻找 parent
的 cancelCtx
类型的祖先 p
,并维护 p
的 children
。
func removeChild(parent Context, child canceler) { if s, ok := parent.(stopCtx); ok { // 解绑回调函数 s.stop() return } p, ok := parentCancelCtx(parent) if !ok { return } p.mu.Lock() if p.children != nil { delete(p.children, child) // 在祖先 ctx 的 children 中删除 child } p.mu.Unlock()}
parentCancelCtx()
一直向上寻找最近的 cancelCtx
祖先,检验后返回。
func parentCancelCtx(parent Context) (*cancelCtx, bool) { done := parent.Done() if done == closedchan || done == nil { return nil, false } p, ok := parent.Value(&cancelCtxKey).(*cancelCtx) if !ok { return nil, false } pdone, _ := p.done.Load().(chan struct{}) if pdone != done { return nil, false } return p, true}
propagateCancel
propagateCancel
的作用一言以蔽之就是将 parent 注册为 c 的父节点,维护祖先 cancelCtx
的 children,将 c 放入到 children 中,形成父链;如果没有找到祖先,则另起额外的协程监听 c 的取消。
func (c *cancelCtx) propagateCancel(parent Context, child canceler) { c.Context = parent
done := parent.Done() if done == nil { return // 父 context 的 done 为 nil,不会 cancel }
select { case <-done: // 父 context 已经被取消,接着取消 child child.cancel(false, parent.Err(), Cause(parent)) return default: }
if p, ok := parentCancelCtx(parent); ok { // 若祖先链路上存在 *cancelCtx p p.mu.Lock() if p.err != nil { // p 已经被取消 child.cancel(false, p.err, p.cause) } else { // 将当前 cancelCtx 注册为 p 的子 cancelCtx // 也就是说,cancelCtx 单独维护一条链,记录 cancelCtx 间的祖先关系,以此减少遍历经过的节点数 if p.children == nil { p.children = make(map[canceler]struct{}) } p.children[child] = struct{}{} } p.mu.Unlock() return }
if a, ok := parent.(afterFuncer); ok { // 如果父 context 实现了 AfterFunc 方法 c.mu.Lock() // 为这个父 context a 注册一个 afterFunc stop := a.AfterFunc(func() { child.cancel(false, parent.Err(), Cause(parent)) }) // 中间插入一个 stopCtx 节点 c.Context = stopCtx{ Context: parent, stop: stop, } c.mu.Unlock() return }
// 起一个协程监听父 context 是否被取消,以判断取消 child goroutines.Add(1) go func() { select { case <-parent.Done(): child.cancel(false, parent.Err(), Cause(parent)) case <-child.Done(): } }()}
afterFuncCtx/stopCtx
// AfterFunc 是 Go 1.21 引入的方法,作用是在 context 被取消时调用函数 f
// 返回的 stop 函数可以终止 ctx 和 f 的关联func AfterFunc(ctx Context, f func()) (stop func() bool) { a := &afterFuncCtx{ f: f, } a.cancelCtx.propagateCancel(ctx, a) return func() bool { stopped := false a.once.Do(func() { stopped = true }) if stopped { a.cancel(true, Canceled, nil) } return stopped }}
type afterFuncer interface { AfterFunc(func()) func() bool}
type afterFuncCtx struct { cancelCtx once sync.Once f func()}
func (a *afterFuncCtx) cancel(removeFromParent bool, err, cause error) { a.cancelCtx.cancel(false, err, cause) if removeFromParent { removeChild(a.Context, a) } a.once.Do(func() { go a.f() })}
// stopCtx 只用于在 cancelCtx 祖先链上无 cancelCtx 但存在 afterFunc 时,充当该 ctx 的父节点// 调用 stop() 后,再取消 ctx 不会再执行 AfterFunc 注册的 ftype stopCtx struct { Context stop func() bool}
WithCancelCause
WithCancelCause
类似 WithCancel
,不同在于返回值是 CancelCauseFunc
而不是 CancelFunc
。
// CancelCauseFunc 相比 CancelFunc 增加了一个取消原因,使用时需要传入一个 error 参数// 可以调用 Cause 来获取具体的原因type CancelCauseFunc func(cause error)
func WithCancelCause(parent Context) (ctx Context, cancel CancelCauseFunc) { c := withCancel(parent) return c, func(cause error) { c.cancel(true, Canceled, cause) }}
// 如果 CancelCauseFunc(err) 设置过取消原因,则返回这个原因// 否则返回 c.Err()func Cause(c Context) error { if cc, ok := c.Value(&cancelCtxKey).(*cancelCtx); ok { cc.mu.Lock() defer cc.mu.Unlock() return cc.cause } return c.Err()}
WithoutCancel
WithoutCancel
返回一个 withoutCancelCtx
,它不会继承父节点的取消语义。除了 Value()
与 String()
以外其他方法行为与 emptyCtx
相同。
//func WithoutCancel(parent Context) Context { if parent == nil { panic("cannot create context from nil parent") } return withoutCancelCtx{parent}}
type withoutCancelCtx struct { c Context}
func (withoutCancelCtx) Deadline() (deadline time.Time, ok bool) { return}
func (withoutCancelCtx) Done() <-chan struct{} { return nil}
func (withoutCancelCtx) Err() error { return nil}
func (c withoutCancelCtx) Value(key any) any { return value(c, key)}
func (c withoutCancelCtx) String() string { return contextName(c.c) + ".WithoutCancel"}
timerCtx
timerCtx
内部嵌入了一个匿名字段 cancelCtx
。与 cancelCtx
中嵌入的 Context
字段不同,这里的嵌入并不是为了保存父节点,而更像是一种面向对象意义上的“继承”——timerCtx
通过组合 cancelCtx
来复用其取消逻辑与状态字段。真正的父节点并不保存在 timerCtx
本身,而是位于其内部的 cancelCtx.Context
字段中,也就是说,c.cancelCtx.Context
才是 timerCtx c
对应的父 context
。
// 当触发取消时,timerCtx 会先停止自身的定时器,然后将取消操作委托给内部的 cancelCtx.cancel()type timerCtx struct { cancelCtx timer *time.Timer // 使用 cancelCtx.mu
deadline time.Time}
func (c *timerCtx) Deadline() (deadline time.Time, ok bool) { return c.deadline, true}
func (c *timerCtx) String() string { return contextName(c.cancelCtx.Context) + ".WithDeadline(" + c.deadline.String() + " [" + time.Until(c.deadline).String() + "])"}
func (c *timerCtx) cancel(removeFromParent bool, err, cause error) { c.cancelCtx.cancel(false, err, cause) if removeFromParent { removeChild(c.cancelCtx.Context, c) } c.mu.Lock() if c.timer != nil { c.timer.Stop() c.timer = nil } c.mu.Unlock()}
WithDeadline()
在创建timerCtx
时,通过time.AfterFunc
注册一个定时回调来触发取消。除了另外需要做一些时间逻辑判断其他实现与 cancelCtx
一致。
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) { return WithDeadlineCause(parent, d, nil)}
func WithDeadlineCause(parent Context, d time.Time, cause error) (Context, CancelFunc) { if parent == nil { panic("cannot create context from nil parent") } if cur, ok := parent.Deadline(); ok && cur.Before(d) { // 如果父节点的 deadline 更早 return WithCancel(parent) } c := &timerCtx{ deadline: d, } c.cancelCtx.propagateCancel(parent, c) dur := time.Until(d) if dur <= 0 { c.cancel(true, DeadlineExceeded, cause) return c, func() { c.cancel(false, Canceled, nil) } } c.mu.Lock() defer c.mu.Unlock() if c.err.Load() == nil { c.timer = time.AfterFunc(dur, func() { c.cancel(true, DeadlineExceeded, cause) }) } return c, func() { c.cancel(true, Canceled, nil) }}
WithTimeout()
WithTimeout
内部计算 deadline = time.Now().Add(timeout)
,并委托给 WithDeadline
创建timerCtx
。
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { return WithDeadline(parent, time.Now().Add(timeout))}
func WithTimeoutCause(parent Context, timeout time.Duration, cause error) (Context, CancelFunc) { return WithDeadlineCause(parent, time.Now().Add(timeout), cause)}
WithValue()
WithValue()
返回一个存储提供的键值对的 valueCtx
,提供的 key 必须为可比较的。
func WithValue(parent Context, key, val any) Context { if parent == nil { panic("cannot create context from nil parent") } if key == nil { panic("nil key") } if !reflectlite.TypeOf(key).Comparable() { panic("key is not comparable") } return &valueCtx{parent, key, val}}
valueCtx
每个valueCtx
携带一组键值对,和 cancelCtx
一样,嵌套一个Context
,但不同的是valueCtx
只重写了Value()
函数,其他函数仍使用 parent 的实现。
type valueCtx struct { Context key, val any}
func (c *valueCtx) String() string { return contextName(c.Context) + ".WithValue(" + stringify(c.key) + ", " + stringify(c.val) + ")"}
func stringify(v any) string { switch s := v.(type) { case stringer: return s.String() case string: return s case nil: return "<nil>" } return reflectlite.TypeOf(v).String()}
func (c *valueCtx) Value(key any) any { if c.key == key { return c.val } return value(c.Context, key)}
// 不断向上寻找直至找到 keyfunc value(c Context, key any) any { for { switch ctx := c.(type) { case *valueCtx: if key == ctx.key { return ctx.val } c = ctx.Context case *cancelCtx: if key == &cancelCtxKey { return c } c = ctx.Context case withoutCancelCtx: if key == &cancelCtxKey { return nil } c = ctx.c case *timerCtx: if key == &cancelCtxKey { return &ctx.cancelCtx } c = ctx.Context case backgroundCtx, todoCtx: return nil default: return c.Value(key) } }}