一般用于创建 root context,这个 context 永远也不会被取消,或超时
TODO(), 底层和 Background 一致,但是含义不同,当不清楚用什么的时候或者是还没准备好的时候可以用它
查看源码我们可以发现,background 和 todo 都是实例化了一个 emptyCtx. emptyCtx又是一个 int类型的别名
type emptyCtx int // emptyCtx分别绑定了四个方法,而这四个方法正是 context接口定义的方法,所以emptyCtx实现了 Context接口 func (*emptyCtx) Deadline() (deadline time.Time, ok bool) { return } func (*emptyCtx) Done() <-chan struct{} { return nil } func (*emptyCtx) Err() error { return nil } func (*emptyCtx) Value(key interface{}) interface{} { return nil }emptyCtx 就如同他的名字一样,全都返回空值
2.2.3 WithCancelWithCancel(), 方法会创建一个可以取消的 context
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { if parent == nil { panic("cannot create context from nil parent") } // 包装出新的 cancelContext c := newCancelCtx(parent) // 构建父子上下文的联系,确保当父 Context 取消的时候,子 Context 也会被取消 propagateCancel(parent, &c) return &c, func() { c.cancel(true, Canceled) } }传入的参数是一个实现了Context接口的类型(root context),且不能为 nil, 所以我们经常使用方式如下:
// context.Background 返回的是一个 root context context.WithCancel(context.Background())不止 WithCancel 方法,其他的 WithXXX 方法也不允许传入一个 nil 值的父 context
newCancelCtx 只是一个简单的包装就不展开了, propagateCancel 比较有意思,我们一起来看看
接下来我们就看看 cancelCtx 长啥样
type cancelCtx struct { Context // 这里保存的是父 Context mu sync.Mutex // 互斥锁 done chan struct{} // 关闭信号 children map[canceler]struct{} // 保存所有的子 context,当取消的时候会被设置为 nil err error }Done()
在 Done 方法这里采用了 懒汉式加载的方式,第一次调用的时候才会去创建这个 channel
func (c *cancelCtx) Done() <-chan struct{} { c.mu.Lock() if c.done == nil { c.done = make(chan struct{}) } d := c.done c.mu.Unlock() return d }Value()
value方法很有意思,这里相当于是内部 cancelCtxKey 这个变量的地址作为了一个特殊的 key,当查询这个 key 的时候就会返回当前 context 如果不是这个 key 就会向上递归的去调用 parent context 的 Value 方法查找有没有对应的值
func (c *cancelCtx) Value(key interface{}) interface{} { if key == &cancelCtxKey { return c } return c.Context.Value(key) }在前面讲到构建父子上下文之间的关系的时候,有一个去查找可以被取消的父 context 的方法 parentCancelCtx 就用到了这个特殊 value
func parentCancelCtx(parent Context) (*cancelCtx, bool) { // 这里先判断传入的 parent 是否是一个有效的 chan,如果不是是就直接返回了 done := parent.Done() if done == closedchan || done == nil { return nil, false } // 这里利用了 context.Value 不断向上查询值的特点,只要出现第一个可以取消的 context 的时候就会返回 // 如果没有的话,这时候 ok 就会等于 false p, ok := parent.Value(&cancelCtxKey).(*cancelCtx) if !ok { return nil, false } // 这里去判断返回的 parent 的 channel 和传入的 parent 是不是同一个,是的话就返回这个 parent p.mu.Lock() ok = p.done == done p.mu.Unlock() if !ok { return nil, false } return p, true }