golang的context本身是按照链式结构组织的,每种context的成员里面都有一个指向父context的结构,源码如下:
type cancelCtx struct {
Context //父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
}
type timerCtx struct {
cancelCtx //父context
timer *time.Timer // Under cancelCtx.mu.
deadline time.Time
}
type valueCtx struct {
Context //父context
key, val interface{}
}
但是,timerCtx和cancelCtx还额外维护了一个map,把所有的子context的cancel方法都存在里面了,这样,父context取消的时候,子context也会跟着取消。
使用context的一个坑,在rpc接口中,如果有长时间执行的任务,在rpc接口返回以后还需要执行,那么,需要自己重新生产一个context,一般的rpc框架,在接口执行完以后,都会取消掉这个context。
context的done本质就是一个标识位,只是做了一些封装而已。
打日志的时候,如果需要打印context中的值,并且context中赋了很多值,有可能会造成性能问题,因为需要回溯的context个数很多。解决办法是自己包一层日志相关的值,放到map里面,然后把map放到context里面。