= doSomething(ctx)
fmt.Println(res, err) // nil , context canceled
}
接下来就让我们来研究下,cancelCtx 是如何实现取消的吧
类型定义
- canceler 接口包含 cancel() 和 Done() 方法,*cancelCtx 和 *timerCtx 均实现了这个接口。
- closedchan 是一个被关闭的channel,可以用于后面 Done() 返回
- canceled 是一个 err,用于 Context 被取消的原因
type canceler interface {
cancel(removeFromParent bool, err error)
Done() <-chan struct{}
}
// closedchan is a reusable closed channel.
var closedchan = make(chan struct{})
func init() {
close(closedchan)
}
var Canceled = errors.New("context canceled")
CancelFunc 是一个函数类型定义,是一个取消函数,有如下规范:
- CancelFunc 告诉一个任务停止工作
- CancelFunc 不会等待任务结束
- CancelFunc 支持并发调用
- 第一次调用后,后续的调用不会产生任何效果
type CancelFunc func()
&cancelCtxKey 是一个固定的key,用来返回 cancelCtx 自身
var cancelCtxKey int
cancelCtx
cancelCtx 是可以被取消的,它嵌套了 Context 接口,实现了 canceler 接口。cancelCtx 使用 children 字段保存同样实现 canceler 接口的子节点,当 cancelCtx 被取消时,所有的子节点也会取消。
type cancelCtx struct {
Context
mu sync.Mutex // 保护如下字段,保证线程安全
done atomic.Value // 保存 channel,懒加载,调用 cancel 方法时会关闭这个 channel
children map[canceler]struct{} // 保存子节点,第一次调用 cancel 方法时会置为 nil
err error // 保存为什么被取消,默认为nil,第一次调用 cancel 会赋值
}
*cancelCtx 的 Value() 方法 和 *valueCtx 的 Value() 方法类似,只不过加了个固定的key: &cancelCtxKey。当key 为 &cancelCtxKey 时返回自身
func (c *cancelCtx) Value(key interface{}) interface{} {
if key == &cancelCtxKey {
return c
}
return c.Context.Value(key)
}
*cancelCtx 的 done 字段是懒加载的,只有在调用 Done() 方法 或者 cancel() 时才会赋值。
func (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{})
}
Err 方法返回 cancelCtx 的 err 字段
func (c *cancelCtx) Err() error {
c.mu.Lock()
err := c.err
c.mu.Unlock()
return err
}
WithCancel
那么我们如何新建一个 cancelCtx呢?context 包提供了 WithCancel() 方法,让我们基于一个 Context 来创建一个 cancelCtx。WithCancel() 方法返回两个字段,一个是基于传入的 Context 生成的 cancelCtx,另一个是 CancelFunc。
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
if parent == nil {
panic("cannot create context from nil parent")
}
c := newCancelCtx(parent)
propagateCancel(parent, &c)
return &c, func() { c.cancel(true, Canceled) }
}
WithCancel 调用了两个外部方法:newCancelCtx 、propagateCancel。newCancelCtx 比较简单,根据传入的 context,返回了一个 cancelCtx 结构体。
func newCancelCtx(parent Context) cancelCtx {
return cancelCtx{Context: parent}
}
propagateCancel 从名字可以看出,就是将 cancel 传播。如果父Context支持取消,那么我们需要建立一个通知机制,这样父节点取消的时候,通知子节点也取消,层层传播。
在 propagateCancel 中,如果 父Context 是 cancelCtx 类型且未取消,会将 子Context 挂在它下面,形成一个树结构;其余情况都不会挂载。
func propagateCancel(parent Context, child canceler) {
// 如果 parent 不支持取消,那么就不支持取消传播,直接返回
done := parent.Done()
if done == nil {
return
}
// 到这里说明 done 不为 nil,parent 支持取消
select {
case <-done:
// 如果 parent 此时已经取消了,那么直接告诉子节点也取消
child.cancel(false, parent.Err())
return
default:
}
// 到这里说明此时 parent 还未取消
// 如果 parent 是未取消的 cancelCtx
if p, ok := parentCancelCtx(parent); ok {
// 加锁,防止并发更新
p.mu.Lock()
// 再次判断,因为有可能上一个获得锁的进行了取消操作。
// 如果 parent 已经取消了,那么子节点也直接取消
if p.err != nil {
child.cancel(false, p.err)
} else {
// 把子Context 挂到父节点 parent cancelCtx 的 children字段下
// 之后 parent cancelCtx 取消时,能通知到所有的 子Context
if p.children ==