context 上下文管理
context 翻译过来就是上下文管理, 主要作用有两个:
控制 goroutine 的超时
保存上下文数据
1 使用
1.1 核心接口Context
type Context interface {// Deadline returns the time when work done on behalf of this context// should be canceled. Deadline returns ok==false when no deadline is// set.Deadline() (deadline time.Time, ok bool)// Done returns a channel that's closed when work done on behalf of this// context should be canceled.Done() <-chan struct{}// Err returns a non-nil error value after Done is closed.Err() error// Value returns the value associated with this context for key.Value(key interface{}) interface{}}
简单介绍一下其中的方法:
- Done会返回一个channel, 当该context被取消的时候, 该channel会被关闭, 同时对应的使用该context的routine也应该结束并返回。
- Context中的方法是协程安全的, 这也就代表了在父routine中创建的context, 可以传递给任意数量的routine并让他们同时访问。
- Deadline会返回一个超时时间, routine获得了超时时间后, 可以对某些io操作设定超时时间。
- Value可以让routine共享一些数据, 当然获得数据是协程安全的。
在请求处理的过程中, 会调用各层的函数, 每层的函数会创建自己的routine, 是一个routine树。所以, context也应该反映并实现成一棵树。
要创建context树, 第一步是要有一个根结点。context.Background函数的返回值是一个空的context, 经常作为树的根结点, 它一般由接收请求的第一个routine创建, 不能被取消、没有值、也没有过期时间。
func Background() Context
之后该怎么创建其它的子孙节点呢? context包为我们提供了以下函数
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 interface{}, val interface{}) Context
这四个函数的第一个参数都是父context, 返回一个Context类型的值, 这样就层层创建出不同的节点。
子节点是从复制父节点得到的, 并且根据接收的函数参数保存子节点的一些状态值, 然后就可以将它传递给下层的routine了。
WithCancel函数, 返回一个额外的CancelFunc函数类型变量, 该函数类型的定义为:
type CancelFunc func()
调用CancelFunc对象将撤销对应的Context对象, 这样父结点的所在的环境中, 获得了撤销子节点context的权利, 当触发某些条件时, 可以调用CancelFunc对象来终止子结点树的所有routine。
在子节点的routine中, 需要用类似下面的代码来判断何时退出routine:
select {case <-cxt.Done():// do some cleaning and return}
根据cxt.Done()判断是否结束。当顶层的Request请求处理结束, 或者外部取消了这次请求, 就可以cancel掉顶层context, 从而使整个请求的routine树得以退出。
WithDeadline和WithTimeout比WithCancel多了一个时间参数, 它指示context存活的最长时间。如果超过了过期时间, 会自动撤销它的子context。所以context的生命期是由父context的routine和deadline共同决定的。
WithValue返回parent的一个副本, 该副本保存了传入的key/value, 而调用Context接口的Value(key)方法就可以得到val。注意在同一个context中设置key/value,若key相同, 值会被覆盖。