() 方法获取的 done channel 与 Value 查找到的 context 的 done channel 是否一致
//
// 一致情况说明 parent context 为 cancelCtx 或 timerCtx 或 自定义的 cancelCtx 且未重写 Done(),
// 这种情况下可以认为拿到了底层的 *cancelCtx
//
// 不一致情况说明 parent context 是一个自定义的 cancelCtx 且重写了 Done() 方法,并且并未返回标准 *cancelCtx 的
// 的 done channel,这种情况需要单独处理,故返回 nil, false
pdone, _ := p.done.Load().(chan struct{})
if pdone != done {
return nil, false
}
return p, true
}
timerCtx
简介
timerCtx 嵌入了 cancelCtx,并新增了一个 timer 和 deadline 字段。timerCtx 的取消能力是复用 cancelCtx 的,只是在这个基础上增加了定时取消而已。
在我们的使用过程中,有可能还没到 deadline,任务就提前完成了,此时需要手动调用 CancelFunc。
func slowOperationWithTimeout(ctx context.Context) (Result, error) {
ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
defer cancel() // 如果未到截止时间,slowOperation就完成了,尽早调用 cancel() 释放资源
return slowOperation(ctx)
}
类型定义
type timerCtx struct {
cancelCtx // 内嵌 cancelCtx
timer *time.Timer // 受 cancelCtx.mu 互斥锁的保护
deadline time.Time // 截止时间
}
Deadline() 返回 deadline 字段的值
func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
return c.deadline, true
}
WithDeadline
WithDeadline 基于parent Context 和 时间点 d,返回了一个定时取消的 Context,以及一个 CancelFunc。返回的Context 有三种情况被取消:1. 到达了指定时间,就会主动取消;2. 手动调用了 CancelFunc;3. 父Context取消,导致该Context被取消。这三种情况哪种先到,就会首次触发取消操作,后续的再次取消不会产生任何效果。
如果传入 parent Context 的 deadline 比指定的时间 d 还要早,此时 d 就没用处了,直接依赖 parent 取消传播就可以了。
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
// 传入的 parent 不能为 nil
if parent == nil {
panic("cannot create context from nil parent")
}
// parent 也有 deadline,并且比 d 还要早,直接依赖 parent 的取消传播即可
if cur, ok := parent.Deadline(); ok && cur.Before(d) {
// The current deadline is already sooner than the new one.
return WithCancel(parent)
}
// 定义 timerCtx 接口
c := &timerCtx{
cancelCtx: newCancelCtx(parent),
deadline: d,
}
// 设置传播,如果parent 属于 cancelCtx,会挂载到 children 字段上
propagateCancel(parent, c)
// 距离截止时间 d 还有多久
dur := time.Until(d)
if dur <= 0 {
// 已经到了截止时间,直接取消,同时从 parent 中取消挂载
// 由于是超时,取消时的 err 是 DeadlineExceeded
c.cancel(true, DeadlineExceeded)
// 再返回 c 和 CancelFunc,已经取消挂载了,此时的 CancelFunc 不会从 parent 中取消挂载
// 后面再次调用 CancelFunc 不会产生任何效果了
// 主动取消的话,err 是 Canceled
return c, func() { c.cancel(false, Canceled) }
}
// 还没有到截止时间,定义一个定时器,过了 dur 会自动取消
c.mu.Lock()
defer c.mu.Unlock()
if c.err == nil {
c.timer = time.AfterFunc(dur, func() {
// 由于是到了截止时间才取消,err 是 DeadlineExceeded
c.cancel(true, DeadlineExceeded)
})
}
// 返回 c 和 cancelFunc,主动取消的 err 是 Canceled
return c, func() { c.cancel(true, Canceled) }
}
接下来我们看下 cancel 方法,timerCtx 的 cancel 方法 就是调用内嵌 cancelCtx 的 cancel() 方法,默认是不从父节点移除
func (c *timerCtx) cancel(removeFromParent bool, err error) {
c.cancelCtx.cancel(false, err)
// 从父节点中移除
if removeFromParent {
removeChild(c.cancelCtx.Context, c)
}
// 把定时器停了,释放资源
// 有可能还没到deadline,手动触发了 CancelFunc,此时把 timer 停了
c.mu.Lock()
if c.timer != nil {
c.timer.Stop()
c.timer = nil
}
c.mu.Unlock()
}
WithTimeout
WithTimeout 就是基于 WithDeadline,deadline 就是基于当前时间计算的
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}
总结
本篇文章,我们通过源码+示例的方式,一起学习了 context 包相关的结构以及实现逻辑,包括如下内容
Context 接口:定义了一些接口方法和规范
emptyCtx:空的Context,Background() 和 TODO() 方法就是使用的 emptyCtx
valueCtx:用于保存键值对,查询时是递归查询,可以用