什么是 context:
context 是golang用来设计的在多个Goroutine中传递上下文信息,包括:取消信号、超时时间、截止时间、k-v 等。
context.Context 类型的值可以协调多个 groutine 中的代码执行“取消”操作,并且可以存储键值对。最重要的是它是并发安全的。
随着 context 包的引入,标准库中很多接口因此加上了 context 参数,例如 database/sql 包。context 几乎成为了并发控制和超时控制的标准做法。
context 在设计上是一个多叉树,且不同的子context,在不同的groutine中是不会有影响的。而且context 实现中有的是基于channel,有的加有 lock所以是线程安全的。
一个context 的典型图
1 | //如:net/http 中就是在 Listener后,产生一个context.Background, |
在使用context的中,如果想监听 取消,或者超时的通知,需要在接受的goruntime里 监听 Context.Done()的方法,来订阅到超时或者取消的通知,或者是没什么用的
1 | go fun(ctx) { |
context的使用场景
- 想在多个goruntime 间传递数据,可以使用 ValueCtx,如 trace id
- 想在多个goruntime 控制时长,可以使用 timerCtx,需要在 多个 goruntime监听context.Done()方法的通知
例如在业务的高峰期,某个下游服务的响应变慢,而当前系统的请求又没有超时控制,或者超时时间设置地过大,那么等待下游服务返回数据的协程就会越来越多。而我们知道,协程是要消耗系统资源的,后果就是协程数激增,内存占用飙涨,甚至导致服务不可用。更严重的会导致雪崩效应,整个服务对外表现为不可用. - 想在多个goruntime 控制取消操作,可以使用 cancelCtx,需要在 多个 goruntime监听context.Done()方法的通知.
当请求被取消,这有可能是使用者关闭了浏览器,请求方直接放弃了这次请求结果。这时,所有正在为这个请求工作的 goroutine 需要快速退出,因为它们的“工作成果”不再被需要了。在相关联的 goroutine 都退出后,系统就可以回收相关的资源.
context的结构
golang 官方默认提供了
- emptyCtx ,其中使用的Background,TODO 等方法都是返回的emptyCtx
- cancelCtx 作为有取消效果的 context
- timerCtx , 依赖于 cancelCtx,提供了时间的控制效果
- valueCtx ,可以传递值的 context
context 接口
接口规定了四个方法 - Deadline() (deadline time.Time, ok bool)
返回代表此上下文完成工作的时间,对于没有截止日期时,截止日期返回 ok==false。
timerCtx 会返回设置的过期时间,以及永远返回true - Done() <-chan struct{}
Done 当在 context 应该被取消的时候,返回一个close 的channel.
WithCancel 当被调用 cancel 的时候,会被 close
WithDeadline 当到期的时候会被 close
WithTimeOut 当超时的时候,会close
Done 用于在 select 语句中使用:1
2
3
4
5
6
7
8
9
10
11
12
13func Stream(ctx context.Context, out chan<- Value) error {
for {
v, err := DoSomething(ctx)
if err != nil {
return err
}
select {
case <-ctx.Done():
return ctx.Err()
case out <- v:
}
}
} - Err() error
当context被关闭的时候,返回关闭的错误提示信息,否则返回nil - Value(key interface{}) interface{}
返回对应key 的value,如果没有匹配的返回nil.
Value会在自身不匹配的时候向父context 匹配查询。直到找到,或者没有匹配的返回nilcanceler 接口
取消的接口定义,是可以直接主动触发取消的定义。
实现是 *cancelCtx 和 *timerCtx - cancel(removeFromParent bool, err error)
removeFromParent 标识是否 从父context 中删除,只在 调用WithCancel返回的 cancel函数里设置为 true.
err 是必传的,默认为 var Canceled = errors.New(“context canceled”) - Done() <-chan struct{} 同 context的Done
在cancelCtx的实现上,chan 是在调用Done方法的时候惰性创建的
emptyCtx
emptyCtx,作为调用 Background,TODO的时候返回的context ,如同它的名字 empty是一个空,无具体作用的 context。一般作为起始的parent context
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
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"
}
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
valueCtx
作为可以在goruntime之间传递值的context,在获取value的时候,如果自身不匹配,会向父类查询。
直至找不到返回 nil
cancelCtx
控制类 cancelCtx, 当想可以控制goruntime的流程的时候,并且通知各个goruntime取消操作的时候使用。
自身具有 children map[canceler]struct{},用来存储子 cancelCtx。
也就是在 调用WithCancel函数的时候,会判断当前 parent context 是否是 cancelCtx,如果是会往parent context的 children添加新建的cancelCtx.
一边,在parent 到期或者取消的时候,parent 会遍历children 调用子context的cancel函数,通知子context取消。
timerCtx
WithDeadline和WithTimeOut 返回的都是timerCtx,其中 WithTimeOut 加上了超时的时间后,又调用了WithDeadline。
当接受到具有 timerCtx的时候,可以调用 Deadline来获取时间作对比,加以判断是否还有足够的时间执行。
而且WithDeadline 在创建的时候,会判断当前时间,如果时间已经大于 超时时间,会创建一个cancelCtx返回。
1 | func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) { |