下面从接口定义、核心数据结构、取消传播机制和值传递机制三方面,深入剖析 Go context
包的底层实现原理。
1. 接口与核心方法
在 context
包中,最核心的是一个接口:
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
- Deadline:返回上下文的截止时间。
- Done:返回一个 channel,当上下文被取消或超时时关闭此 channel。
- Err:当上下文结束时,返回
Canceled
或DeadlineExceeded
。 - Value:从上下文链上检索与
key
对应的值。
所有上下文类型都必须实现这四个方法。
2. 核心数据结构
2.1 根上下文
Background
和TODO
都是全局唯一的空上下文,底层是一个零值的emptyCtx
:type emptyCtx struct{} func (emptyCtx) Deadline() (time.Time, bool) { return } func (emptyCtx) Done() <-chan struct{} { return nil } func (emptyCtx) Err() error { return nil } func (emptyCtx) Value(key interface{}) interface{} { return nil }
2.2 取消与超时上下文
- 取消型:
WithCancel(parent)
返回一个cancelCtx
- 超时型:
WithDeadline(parent, d)
/WithTimeout(parent, dt)
返回一个timerCtx
它们都在底层扩展了父上下文:
type cancelCtx struct {
Context // 嵌入父 Context
mu sync.Mutex // 保护以下字段
done chan struct{}// 取消信号 channel
children map[canceler]struct{}
err error // 存储取消原因
}
type timerCtx struct {
cancelCtx // 继承 cancelCtx 的机制
timer *time.Timer // 额外的定时器
}
关键字段说明
done chan struct{}
:一旦close(done)
,Done()
的接收者就能感知到。err error
:存储取消原因,Err()
返回ctx.err
。children map[canceler]struct{}
:用于将取消信号向下传播给所有子上下文。
2.3 值上下文
WithValue(parent, key, val)
返回一个valueCtx
:type valueCtx struct { Context // 嵌入父 Context key, val interface{} // 存储单个键值对 }
3. 取消传播与同步
3.1 注册子上下文
当你调用 WithCancel(parent)
,会向父 cancelCtx
注册自己:
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
c := &cancelCtx{Context: parent, done: make(chan struct{})}
propagateCancel(parent, c) // 将 c 加入 parent 的 children
return c, func(){ c.cancel(true, Canceled) }
}
propagateCancel
会沿着父链,找到第一个支持 “注册子” 的上下文(即cancelCtx
或timerCtx
),并将新节点加入其children
。
3.2 触发取消
当调用 cancel()
或者超时定时器触发时,执行 cancelCtx.cancel()
:
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
c.mu.Lock()
if c.err != nil {
c.mu.Unlock()
return // 已经取消过
}
c.err = err
close(c.done)
for child := range c.children {
child.cancel(false, err) // 向下递归取消
}
c.children = nil
c.mu.Unlock()
if removeFromParent {
removeChild(parent, c)
}
}
- 去重:若已取消,则直接返回。
- 关闭
done
:通知所有监听者。 - 递归取消:逐层通知所有子上下文。
- 从父节点解除注册:避免内存泄露。
3.3 同步细节
done
channel 只被关闭一次,无阻塞读写;- 读取
Err()
时,只要done
被关闭,就能拿到非nil
的err
; - 锁
mu
保护children
、err
,保证并发安全。
4. 值传递机制
WithValue
并不具备取消功能,它只是把一个键值对链到上下文树上。其实例结构:
type valueCtx struct {
Context
key, val interface{}
}
执行 ctx.Value(k)
时,会递归往上(通过嵌入的父 Context)查找,直到:
- 找到
valueCtx
的key == k
,则返回对应的val
; - 走到根
emptyCtx
,返回nil
。
5. 小结
- 组合与嵌入:所有上下文类型通过嵌入(
Context
接口)形成一棵链式树。 - 取消信号传播:基于
cancelCtx
节点的done
channel 与children
列表,通过递归及锁机制,实现可靠的取消传播与清理。 - 超时支持:
timerCtx
在cancelCtx
的基础上添加定时器,定时触发相同的取消逻辑。 - 值传递:
valueCtx
只负责存储单个键值对,并通过链式查找实现继承。