emptyCtx,所有ctx的根,用context.TODO(),或context.Background()来生成。
type emptyCtx int
valueCtx,主要就是为了在ctx中嵌入上下文数据,一个简单的k和v结构,同一个ctx内支持一对kv,需要更多的kv的话,会形成一颗树形结构
type valueCtx struct {
Context
key, val interface{}
}
func (e *emptyCtx) String() string {
switch e {
case background:
return “context.Background”
case todo:
return “context.TODO”
}
return “unknown empty Context”
}
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
cancelCtx,用来取消程序的执行树,一般用WithCancel,WithTimeout,WithDeadline 返回的取消函数本质上都是对应了cancelCtx
type cancelCtx struct {
Context
done chan struct{} // closed by the first cancel call.
mu sync.Mutex
children map[canceler]bool // set to nil by the first cancel call
err error // set to non-nil by the first cancel call
}
timerCtx,在cancelCtx上包了一层,支持基于时间的cancel
type timerCtx struct {
*cancelCtx
timer *time.Timer // Under cancelCtx.mu.
deadline time.Time
}
使用emptyCtx初始化context
用来实现context.TODO()和context.Background(),一般是所有context的根
func (e *emptyCtx) String() string {
switch e {
case background:
return "context.Background"
case todo:
return "context.TODO"
}
return "unknown empty Context"
}
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
todo和background两者本质上只有名字区别,在按string输出的时候会有区别
valueCtx嵌入数据
valueCtx主要就是用来携带整个逻辑流程的数据,在分布式系统中最常见的就是trace_id,在一些业务系统中,一些业务数据项也需要贯穿整个请求的生命周期,如order_id,payment_id
key必须为空,且可比较。
在查找值,即执行value操作时,会先判断当前节点的k是不是等于用户的输入k,如果相等,返回结果,如果不等,回一次向上从子节点向父节点,一直查找到整个ctx的根。没有找到返回nil,是一个递归的流程。
通过分析,ctx这么设计是为了能让代码每执行一个点都可以根据当前嵌入新的上下文信息,如果每次加一个新值都执行WithValue会导致ctx的树的层数过高,查找成本比较高
很多业务场景中,我们希望在请求入口存入值,在请求过程中随时取用,将value作为一个map整体存入。