context
包是Go 语言中用来设置截止日期、同步信号,传递请求相关值的结构体,是开发常用的并发控制技术。
与WaitGroup
的不同在于context
可以控制多级的goroutine
。
1. 接口定义
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <- chan struct{}
Err() error
Value(key interface{}) interface{}
}
Deadline(): 工作的截止时间,没有设置
deadline
则ok==false
。
Done(): 需要在select-case
语句中使用(case <-context.Done():
)。当context
被关闭后,Done()
返回一个被关闭的通道(关闭的通道依然是可以读的,所以goroutine
可以收到关闭请求);当context
还未关闭时,Done()
返回nil
。
Err(): 描述context
关闭的原因,其原因由context
实现控制。例如:因deadline
关闭:context deadline exceeded
;因主动关闭:context canceled
。没有关闭时,返回nil
。
Value(): 特别的用于一种context
:不用于控制呈树状分布的goroutine
,而是用于在树状分布的goroutine
之间传递信息。Value()
方法根据key
值查询map
中的Value
。
2. 使用
一个案例来展示context
的使用,它做了2件事:1. 创建过期时间为1s的上下文。 2. 将context
传入handle
函数中,函数使用500ms的时间处理请求。
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
go handle(ctx, 500*time.Millisecond)
select {
case <-ctx.Done():
fmt.Println("main", ctx.Err())
}
}
func handle(ctx context.Context, duration time.Duration) {
select {
case <-ctx.Done():
fmt.Println("handle", ctx.Err())
case <-time.After(duration):
fmt.Println("process request with", duration)
}
}
output:
process request with 500ms
main context deadline exceeded
分析: context
过期时间为1s,处理时间为0.5秒(select
中的过期时间),函数有足够的时间完成处理,也就是<-time.After(duration):
会在<-ctx.Done()
之前完成,故输出process request with 500ms
。再过0.5s,<-ctx.Done()
完成,这时候输出main context deadline exceeded
。
倘若,代码中的``改为400*time.Millisecond
,会输出什么呢?
A:
main context deadline exceeded
B:
main context deadline exceeded
handle context deadline exceeded
C:
process request with 500ms
main context deadline exceeded
D:
process request with 500ms
E:
handle context deadline exceeded
main context deadline exceeded
答案是:A、B、E
可能出现这3种,而不是1种的原因是和调度器有关。
context
的一些方法:
-
默认上下文: 以下两个方法都会返回预先初始化好的私有变量
background
和todo
,它们会在同一个 Go 程序中被复用。这两个私有变量都是通过new(emptyCtx)
语句初始化的,它们是指向私有结构体context.emptyCtx
的指针,这是最简单、最常用的上下文类型。-
context.Background()
: 是上下文的默认值,所有其他的上下文都应该从它衍生出来。 -
context.TODO()
: 应该仅在不确定应该使用哪种上下文时使用。
-
-
取消信号:前两个创建的是
context.WithCancel
,最后一个创建的是context.timerCtx
。-
context.WithCancel()
: 由context.Context()
衍生出的特殊的子上下文。一旦它的返回函数被执行,其所有子context
将都会被返回。 -
context.WithDeadline()
: 在某个时间点进行返回。 -
context.WithTimeout()
: 某个时间段过后进行返回。
-
-
传值方法:
-
context.WithValue()
: 从父上下文中创建一个子上下文,传值的子上下文使用context.valueCtx
。
需要注意的是这个方法是递归的根据Key
来获取Value
的。
-
func (c *valueCtx) Value(key interface{}) interface{} {
if c.key == key {
return c.val
}
return c.Context.Value(key)
}