入 key 和 value,通过 Value() 方法则可以根据 key 拿到 value。
func main() {
ctx := context.Background()
c := context.WithValue(ctx, "key", "value")
v, ok := c.Value("key").(string)
fmt.Println(v, ok)
}
emptyCtx
Context 接口并不需要我们自己去手动实现,一般我们都是直接使用 context 包中提供的 Background() 方法和 TODO() 方法,来获取最基础的 Context。
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
func Background() Context {
return background
}
func TODO() Context {
return todo
}
Background() 方法一般用在 main 函数,或者程序的初始化方法中;在我们不知道使用哪个 Context,或者上文没有传递 Context时,可以使用 TODO()。
Background() 和 TODO() 都是基于 emptyCtx 生成的,从名字可以看出来,emptyCtx 是一个空的Context,没有 deadline、不能被取消、没有键值对。
type emptyCtx int
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
}
func (e *emptyCtx) String() string {
switch e {
case background:
return "context.Background"
case todo:
return "context.TODO"
}
return "unknown empty Context"
}
除了上面两个最基本的 Context 外,context 包中提供了功能更加丰富的 Context,包括 valueCtx、cancelCtx、timerCtx,下面我们就挨个来看下。
valueCtx
使用示例
我们一般使用 context.WithValue() 方法向 Context 存入键值对,然后通过 Value() 方法根据 key 得到 value,此种功能的实现就依赖 valueCtx。
func main() {
ctx := context.Background()
c := context.WithValue(ctx, "myKey", "myValue")
v1 := c.Value("myKey")
fmt.Println(v1.(string))
v2 := c.Value("hello")
fmt.Println(v2) // nil
}
类型定义
valueCtx 结构体中嵌套了 Context,使用 key 、value 来保存键值对:
type valueCtx struct {
Context
key, val interface{}
}
WithValue
context包 对外暴露了 WithValue 方法,基于一个 parent context 来创建一个 valueCtx。从下面的源码中可以看出,key 必须是可比较的!
func WithValue(parent Context, key, val interface{}) Context {
if parent == nil {
panic("cannot create context from nil parent")
}
if key == nil {
panic("nil key")
}
if !reflectlite.TypeOf(key).Comparable() {
panic("key is not comparable")
}
return &valueCtx{parent, key, val}
}
*valueCtx 实现了 Value(),可以根据 key 得到 value。这是一个向上递归寻找的过程,如果 key 不在当前 valueCtx 中,会继续向上找 parent Context,直到找到最顶层的 Context,一般最顶层的是 emptyCtx,而 emtpyCtx.Value() 返回 nil。
func (c *valueCtx) Value(key interface{}) interface{} {
if c.key == key {
return c.val
}
return c.Context.Value(key)
}
cancelCtx
cancelCtx 是一个用于取消任务的 Context,任务通过监听 Context 是否被取消,来决定是否继续处理任务还是直接返回。
如下示例中,我们在 main 函数定义了一个 cancelCtx,并在 2s 后调用 cancel() 取消 Context,即我们希望 doSomething() 在 2s 内完成任务,否则就可以直接返回,不需要再继续计算浪费资源了。
doSomething() 方法内部,我们使用 select 监听任务是否完成,以及 Context 是否已经取消,哪个先到就执行哪个分支。方法模拟了一个 5s 的任务,main 函数等待时间是2s,因此没有完成任务;如果main函数等待时间改为10s,则任务完成并会返回结果。
这只是一层调用,真实情况下可能会有多级调用,比如 doSomething 可能又会调用其他任务,一旦 parent Context 取消,后续的所有任务都应该取消。
func doSomething(ctx context.Context) (interface{}, error) {
res := make(chan interface{})
go func() {
fmt.Println("do something")
time.Sleep(time.Second * 5)
res <- "done"
}()
select {
case <-ctx.Done():
return nil, ctx.Err()
case value := <-res:
return value, nil
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
go func() {
time.Sleep(time.Second * 2)
cancel()
}()
res, err :