理解golang的context包

概述

golang的context包,中文有叫做上下文的,是对程序单元运行环境的一个整体的描述.这个概念很抽象,但是在golang里,主要是用来在调用层级间传递需要用到的请求数据等.当然传递数据不是可以用channel吗?这也是可以的,但是context主要是解决一个层级的关系问题.当顶层调用取消,后面的请求也应该全部取消.这一点我们可以通过信号来实现,其实官方也是通过一个channel来监控的.

具体使用

下午看了下context包源码,代码很简单.大概理解如下记录,方面以后的查漏补缺.

context.Context中两个重要的interface以及四种类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// A canceler is a context type that can be canceled directly. The
// implementations are *cancelCtx and *timerCtx.
type canceler interface {
cancel(removeFromParent bool, err error)
Done() <-chan struct{}
}
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
//这四种类型其实就是我们常用到的context
type emptyCtx int//最基础的context
type cancelCtx struct {
Context
done chan struct{} // closed by the first cancel call.
mu sync.Mutex
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
timer *time.Timer // Under cancelCtx.mu.
deadline time.Time
}
type valueCtx struct {
Context
key, val interface{}
}

我们在使用context的时候大部分都是在顶端使用

1
func Background() Context

方法来创建顶级context.和context.TODO()方法一样,他们返回的context其实就是emptyCtx.没有value,也没有deadline.
而实际使用过程中我们还会使用下面的四个方法

1
2
3
4
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key, val interface{}) Context

其中cancel CancelFunc是一个function,可以通过使用他来cancel对应的context.这四个里WithValue是最简单的,它仅仅是在parentContext的基础上附加了一个key值以及对应的value,正如上面valueCtx所展示的那样,是一个struct{},其中的Context就是parentContext.获取你附加的value也很简单,使用Value(key interface{}) interface{}即可,他会自下而上的逐层查看直到返回与key对应的value.

而其他的三个方法其实都是建立在cancelCtx上的,其中WithTimeout就是调用的WithDeadline方法,而WithDeadline其实就是在cancelCtx的基础上增加了deadline以及timer这里就不再描述.

最后也是最重要的context的父子关系或者说层级关系.实际上CancelFunc的函数体是这样的:

1
func() { c.cancel(true, Canceled)

c就是方法即将返回的context,他同时也实现了canceler接口,Canceled则是自定义的error:var Canceled = errors.New("context canceled")
第一个参数是指的在cancel的时候是否将其从父context的孩子节点中移除,默认是true.这里的children用一个map来存储,在需要的时候delete即可.

context.go文件里的代码真的很少,其实主要的就是上面讲的几个方法或者type,剩下的都是一些辅助方法比如如何返回一个子级上下文的父级上下文以及在cancel的时候注意遍历下子级上下文重复cancel的动作或者是否removeFromParent.而这里的从父级上下文中移除实际就是在cancel之后操作map做delete().这是下午单位断电后抽空拿本子看了下代码,本身博客就没写多少中间还隔了一段时间没写,感觉完全不知道写什么,很尴尬,后面在多写多练习吧.最后写下go doc中对于使用context的几点建议吧

  • 不要将需要的传递的context包含在一个struct中,而且在需要传递context的函数参数列表中将context放在最前面作为第一个参数,就好像下面写的这样:func DoSomething(ctx context.Context, arg Arg) error
  • 哪怕方法允许也不要传递一个nil的Context,如果确实不确定使用哪种context,请使用context.TODO()
  • context的Value仅仅用在程序以及API交互的请求周期中传递的数据上,别用在给函数传递一些可选的参数
  • context可以传递给不同goroutine来执行,同时在多个goroutine中使用是安全的
    (原谅我的渣翻译)
客官扫码领红包哟~