go语言中context库里propagateCancel函数
// 设置当父context取消时候,子context也取消的逻辑
func propagateCancel(parent Context, child canceler) {
//父context永远不会被取消(例如WithValue)
done := parent.Done()
if done == nil {
return
}
select {
case <-done:
// 父context已经被取消(例如父context已经被cancel,而子协程中才使用withcancel(ctx))
child.cancel(false, parent.Err())
return
default:
}
//确定parent最内层的cancel是否是内部实现的cancelCtx
//如果是,则把child放入该cancelCtx的child中
if p, ok := parentCancelCtx(parent); ok {
p.mu.Lock()
if p.err != nil {
// parent has already been canceled
child.cancel(false, p.err)
} else {
if p.children == nil {
p.children = make(map[canceler]struct{})
}
p.children[child] = struct{}{}
}
p.mu.Unlock()
} else {
//如果不是内部实现的cancelCtx则另外起一个协程
//一直监听父context是否关闭,如果关闭则关闭child
atomic.AddInt32(&goroutines, +1)
go func() {
select {
case <-parent.Done():
child.cancel(false, parent.Err())
case <-child.Done():
}
}()
}
}
上面判断parentCancelCtx这个,因为并非所有人实现的context都有children ,当是golang内部实现的cancelCtx时候,可以添加child来让parent取消。而如果是自己实现的parent-context,则一定是让child监听parent-done来观察parent是否结束。
如何判断parent是否是cancelCtx
// &cancelCtxKey is the key that a cancelCtx returns itself for.
var cancelCtxKey int
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
done := parent.Done()
if done == closedchan || done == nil {
return nil, false
}
//cancelCtx 的 value方法会首先检查 key == &cancelCtxKey
//如果是则返回自己
//挺有意思的一个思路
p, ok := parent.Value(&cancelCtxKey).(*cancelCtx)
if !ok {
return nil, false
}
p.mu.Lock()
ok = p.done == done
p.mu.Unlock()
if !ok {
return nil, false
}
return p, true
}