go 1.23.4
type Context interface { // 当 context 被取消或者到了 deadline,返回一个被关闭的 channel Done() <-chan struct{} // 在 channel Done 关闭后,返回 context 取消原因 Err() error // 返回 context 是否会被取消以及自动取消时间(即 deadline) Deadline() (deadline time.Time, ok bool) // 获取 key 对应的 value Value(key interface{}) interface{} }

type cancelCtx struct { Context mu sync.Mutex // protects following fields done atomic.Value // of chan struct{}, created lazily, closed by first cancel call children map[canceler]struct{} // set to nil by the first cancel call err error // set to non-nil by the first cancel call cause error // set to non-nil by the first cancel call }
cancelCtx嵌入了一个Context,继承了Context的所有特性,所以cancelCtx可以被看成一个Context
done采用懒加载的方式创建
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{}) }
再是cancel
// closedchan is a reusable closed channel. var closedchan = make(chan struct{}) // cancel closes c.done, cancels each of c's children, and, if // removeFromParent is true, removes c from its parent's children. // cancel sets c.cause to cause if this is the first time c is canceled. func (c *cancelCtx) cancel(removeFromParent bool, err, cause error) { if err == nil { panic("context: internal error: missing cancel error") } if cause == nil { cause = err } c.mu.Lock() if c.err != nil { c.mu.Unlock() return // already canceled } c.err = err c.cause = cause d, _ := c.done.Load().(chan struct{}) if d == nil { c.done.Store(closedchan) } else { close(d) } for child := range c.children { // NOTE: acquiring the child's lock while holding parent's lock. child.cancel(false, err, cause) } c.children = nil c.mu.Unlock() if removeFromParent { removeChild(c.Context, c) } }
用c.done.Store(closedchan)关闭chan,递归取消所有子节点。
可以看到在取消子节点child.cancel(false,err,cause)中传入了false,因为后面执行了c.child = nil,没必要在子节点取消时多做一步。
上面知道了cancelCtx可以作为Context使用,看看怎么创建可取消的Context
// WithCancel returns a copy of parent with a new Done channel. The returned // context's Done channel is closed when the returned cancel function is called // or when the parent context's Done channel is closed, whichever happens first. // // Canceling this context releases resources associated with it, so code should // call cancel as soon as the operations running in this [Context] complete. 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 }
这里可以看到c.cancel(true,Canceled,nil)传入了true。
Context是怎么在Go语言中发挥关键作用的 深度解密Go语言之context