最近在context的源码,看到这么一段,其中不太理解这个progageteCancel里的两个if,第一个parent.Done() == nil 按说已经过滤出background和todo了,后面再用parentCancelCtx为啥还要来一次,按说除了background和todo外的其他context也不会走到这里撒。
这里难道是为了让开发者可以自己去实现Context这个interface,然后通过WithCancel这样的方式来衔接后续的动作,这么做有什么具体意义吗?
```golang
// 这一块实现的是保证cancel链路完整,结束动作能够顺利沿着链路传递下去
func propagateCancel(parent Context, child canceler) {
// 这里找到父节点的Done,如果是nil显然只能是background或者todo了撒
if parent.Done() == nil {
return // parent is never canceled
}
// 这里会调用parentCancelCtx,我个人理解是从链路上找到一个cancelCtx
// 但是看源码基本也识别了所有类型了,剩下的无非也就是background和todo
// 了,是不是说这里不可能进到else呢?
if p, ok := parentCancelCtx(parent); ok {
p.mu.Lock()
// 如果父节点已经被cancel掉了,就把字节点也cancel掉
if p.err != nil {
// parent has already been canceled
child.cancel(false, p.err)
} else {
if p.children == nil {
// 因为golang没有set,所以这里拿map做为set用
// 这里也看到了,实际上p.children下存的并不是完整的
// 子节点,只是留下了部分在关闭时候需要用到的方法罢了
// 可以理解canceler是cancelCtx的一个子集
p.children = make(map[canceler]struct{})
}
// 记录子节点
p.children[child] = struct{}{}
}
p.mu.Unlock()
} else {
// 开一个goroutine去监控,如果父节点结束了,就关掉子节点
// 如果子节点结束了就算了
go func() {
select {
case <-parent.Done():
child.cancel(false, parent.Err())
case <-child.Done():
}
}()
}
}
// 认识所有的context,从parent对象上找一个cancelCtx,用来衔接cancel链路
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
for {
switch c := parent.(type) {
case *cancelCtx:
return c, true
case *timerCtx:
return &c.cancelCtx, true
case *valueCtx:
parent = c.Context
default:
return nil, false
}
}
}
```
有疑问加站长微信联系(非本文作者)