Go中的Context
Context接口结构
Go的Context在context包下,Context是一个接口,结构如下
type Context interface {
Value(key interface{}) interface{}
Err() error
Done() <-chan struct{}
Deadline() (deadline time.Time, ok bool)
}
下面一一说明每个函数的主要功能和用法:
- Value函数:根据key返回其所对应的value的值,在实现Context接口时,可在底层嵌套一个Map来实现
- Done函数:Context一般被描述为一个长时间执行的任务的上下文,在任务被cancel时Done返回的chan回传一个消息,告知外部任务被cancel了
- Err函数:对应下面的Done函数,在任务被cancel时,Context使用Err函数返回一些被cancel的错误信息
- Deadline函数:当Context被设置了超时时间时,Deadline返回的ok为true,并且deadline返回具体的超时时间;如果没有设置,则ok返回为false
如上四个函数,可以看出Context的主要作用有三个:1. 传递上下文数据、2. 任务取消时通知和广播、3. 超时机制
Context的一些具体实现
emptyContext
最简单的实现就是emptyContext了,其对Context接口的四个函数实现均为空实现,并且其命名是首字母小写,也就是说我们在代码中其实是无法引用的。那emptyContext有啥作用呢?emptyContext既然没有为Context的四个函数实现功能,那就说明没有功能的Context就可以使用emptyContext。context包提供了Background和TODO两个函数来返回emptyContext的实例,用于构造默认的Context。
valueContext
type valueCtx struct {
Context
key, val interface{}
}
func (c *valueCtx) Value(key interface{}) interface{} {
if c.key == key {
return c.val
}
return c.Context.Value(key)
}
valueContext主要是用来实现Context接口的Value函数功能的。valueContext有三个成员,分别是匿名的Context和key/value的interface{}变量,它真的只实现了Value函数,其他三个函数都交由匿名的Context去实现。因此valueContext其实只能保存一对key-value键值对。
valueContext的创建使用context包的WithValue(parent Context, key, value interface{})函数,其中parent这个Context就是valueContext的匿名Context,相当于valueContext继承这个parent的Context。调用Value函数传入的key如果和创建valueContext时传入的key不一致,则会调用parent的Value函数继续向上查找。
这里可以看到,context包下对于Context接口的实现使用了包装+责任链模式,每一个Context都可将一个parent的Context包装起来并实现一些功能,如果得到的Context实例无法工作,则调用其parent的同名函数。这样Context就形成了一条链,有点类似于Java中的ClassLoader的实现模式。
同时,由于valueContext也是个首字母小写的struct,其没有提供任务key-value修改的函数,因此valueContext是一个只读的key-value对。当我们在函数调用传入一个context需要带一些key-value参数时,就可以使用context#WithValue来将原始的context实例包装一层。
cancelContext
cancelContext实现了一个可取消任务的Context,使用context#WithCancel(parent Context)(ctx Context, cancel CancelFunc)函数来创建。值得注意的是WithCancel函数返回了CancelFunc类型的变量,这是一个函数类型,在调用这个函数的时候,会触发cancelContext的cancel函数。在cancel函数中,会将Done函数返回的chan给close掉并且调用子Context的cancel函数。
timerContext
timerContext使用context#WithDeadline(parent Context,d time.Time) (Context, CancelFunc)创建,和cancelContext相比,创建的函数多了一个time.Time参数,表示在时间d的时候,将自动触发任务完全,相当于是一个定时器,或者是一个定时取消的Context,当然我们也可以调用返回的CancelFunc函数来手动触发任务取消,timerContext相当于是cancelContext的升级版。
Context的使用建议
golang官方是建议我们在任何有耗时操作的函数中,都在第一个函数形参传入一个Context变量,用来方便地控制和感知耗时任务的执行情况。并且,由于context的包装+责任链模式,使得我们可以非常方便地对context进行扩展,在函数层层调用时,我们可以在每一层给context加一些参数和功能。
同时,我们在一些比较重资源的函数调用时,比如说一个http请求的handler函数中,也可以将http框架的context使用context.Context接口来封装一下,使得http框架的context也能得被非http框架的类库处理。
其实其他一些框架中已经实现了类似于context包中的一些功能,只不过go把这个context最基本的功能给内置了,如果内置了,我们当然是使用内置的好,大家都遵守内置的context的接口规范,不同类型就可以使用相同的接口进行通信和调用了。