这是一个新的分类:Golang 牛逼之路
Go 的 context 包是并发编程的核心工具,他的设计通过统一接口简化了 goroutine 的生命周期管理。其实核心就在于 context.Context 接口的四个方法:Deadline()、Done()、Err() 和 Value(key)。但是呢 cancelCtx 和 timerCtx 是其实现的关键结构,分别用于处理主动取消和超时取消。
取消流程
cancelCtx 是支持取消操作的核心结构,源码:
type cancelCtx struct {
Context
mu sync.Mutex
done atomic.Value
children map[canceler]struct{}
err error
}
done channel
当调用 cancel() 方法的时候,他会首先会标记错误状态(比如说 Canceled 或 DeadlineExceeded),并通过 closedchan 去关闭 done channel。具体步骤如下:
- 原子操作保护 :使用互斥锁
mu确保并发安全,防止多次调用cancel()。 - 错误状态标记 :将
err字段设置为指定错误值(如Canceled),后续调用Err()会返回该值。 - 关闭
donechannel :通过closedchan(一个已关闭的空结构体 channel)替换原donechannel,触发响应 。
取消子节点
cancelCtx 还维护了一个子节点集合(children map[canceler]struct{}),当调用 cancel() 时会遍历所有子节点并递归调用它们的 cancel() 方法。简单来说只要执行了这个,那么就能确定取消信号能在父节点之间传播下去了:
for child := range c.children {
child.cancel(false, err, false)
}
- 子节点类型 :子节点可能是
cancelCtx或timerCtx,均实现canceler接口。 - 传播机制 :递归调用保证父节点取消时,所有子节点同步被取消
从父节点删除自己
假设当前 cancelCtx 的父节点也是可取消的contetxt(如 cancelCtx 或 timerCtx),那么就去调用 removeChild 从父节点的子节点集合中把自己删掉,释放资源:
if p, ok := c.Context.(*cancelCtx); ok {
p.mu.Lock()
if s, ok := p.children[c]; ok {
delete(p.children, c)
}
p.mu.Unlock()
}
懒加载
done channel 用的是懒加载策略(通过 atomic.Value 存储),只会在首次调用 Done() 的时候去初始化。通过这个方式可以优化内存占用,但是还是需要在并发访问时确保原子性 。
timerCtx 的超时处理
timerCtx 直接继承了 cancelCtx,通过增加一个定时器字段就可以实现超时取消:
type timerCtx struct {
cancelCtx
timer *time.Timer
deadline time.Time
}
WithDeadline
当调用 context.WithDeadline(parent Context, deadline time.Time) 的时候,发生了:
- 计算剩余时间 :假设
deadline过了,那么立即取消;否则计算剩余时间d := t.deadline.Sub(now)。 - 启动定时器 :创建一个
time.Timer,在d时间后触发cancel()方法:
t.timer = time.AfterFunc(d, func() {
t.cancel(true, DeadlineExceeded, false)
})
- 传播机制 :超时触发后,
timerCtx会调用cancel(),继承cancelCtx的递归取消逻辑。
提前取消与资源释放
如果说定时器触发前就调用 cancel() 的话,就得手动停止定时器以避免资源泄漏:
func (c *timerCtx) cancel(removeFromParent bool, err error, manual bool) {
c.cancelCtx.cancel(removeFromParent, err, manual)
if c.timer.Stop() {
// 定时器未触发,释放资源
}
}
这个里面还涉及了竞态处理:
- 竞态处理 :
Stop()返回布尔值,确保定时器未触发时才释放资源,避免 race condition。
总结
context 的设计通过接口抽象还有组合模式实现了简洁高效的并发控制。cancelCtx 的递归取消和 timerCtx 的定时器机制共同实现了 Go 并发模型的核心能力。
当然了你如果不想用的话也是可以手动传递 channel 实现取消通知之类的方法啦。