Golang context
Context数据结构
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
Context是一个接口类型。
Deadline():当ctx有结束时间时,返回结束时间和true。WithTimeout和WithDeadline方法都有结束时间。
Done():返回一个<-chan。当触发超时或手动取消时,chan会关闭。Done可以在多个协程中执行,因为它是幂等的。
Err():如果chan关闭返回关闭的原因。
Value():获取上下文中的值。
方法
Background():默认的上下文,一般用作初始化根上下文。
TODO():TODO()的实现跟Background()一样,从语义上区分,TODO()仅用在不确定应该使用哪种上下文时使用。
WithCancel():从传入的Context衍生出一个新的上下文,当执行取消函数,当前上下文以及它的子上下文都会被取消。
WithTimeout():给上下文设定一个超时的时间段,当到达超时时间或者手动执行取消函数时,当前上下文以及它的上下文都会被取消。
WithDeadline():给上下文设定一个结束时间,当到达超时时间或者手动执行取消函数时,当前上下文以及它的上下文都会被取消。
WithValue():传入一个键值对,从Context衍生出一个上下文,并且携带该键值对,该上下文以及它的字上下文可以通过Value()方法,获取到值。
遵循规则
遵循以下规则,以保持包之间的接口一致,并启用静态分析工具以检查上下文传播。
- 不要将 Contexts 放入结构体,相反
context
应该作为第一个参数传入,命名为ctx
。func DoSomething(ctx context.Context,arg Arg)error { // ... use ctx ... }
- 即使函数允许,也不要传入
nil
的 Context。如果不知道用哪种 Context,可以使用context.TODO()
。 - 使用context的Value相关方法只应该用于在程序和接口中传递的和请求相关的元数据,不要用它来传递一些可选的参数
- 相同的 Context 可以传递给在不同的
goroutine
;Context 是并发安全的。
应用场景
函数链路传值
func main(){
ctx:=context.WithValue(context.Background(),"req_id","uid1")
Do(ctx)
}
func Do(ctx context.Context){
reqID:=ctx.Value("req_id")
fmt.Println("req_id = ",reqID)
Do2(ctx)
}
func Do2(ctx context.Context){
reqID:=ctx.Value("req_id")
fmt.Println("req_id = ",reqID)
// do something
}
函数需要用到context作为参数时,context需要作为函数的第一个参数。
控制goroutine的超时退出
比如有一个比较耗时的函数,我们希望这个函数如果执行超过两秒就结束并报告一个执行超时的错误。
func work(ctx context.Context)error{
time.Sleep(10*time.Second)
return nil
}
// workWithTimeout
func workWithTimeout(ctx context.Context)error{
ctx,cancel := context.WithTimeout(ctx,2*time.Second)
defer cancel()
workResult := make(chan error,1)
go func() {
workResult<-work(ctx)
}()
select {
case result :=<-workResult:
return result
case <-ctx.Done():
return ctx.Err()
}
}
func main(){
ctx:=context.Background()
err:=workWithTimeout(ctx)
if err!=nil{
fmt.Println(err)
}else{
fmt.Println("work done")
}
}
workWithTimeout,先初始化了一个error的channel用来接收work执行的结果,使用select+case,如果2秒内work执行完了,就返回结果result,同时通过defer关闭ctx,否则ctx还会等到超时后才会取消;如果超时了work也没执行完成,就返回ctx的错误信息。
通知goroutine退出
使用goroutine执行一个监控程序,当接收到退出信号时,退出监控。主进程需要等到所有goroutine都结束后,才能退出。
func main() {
ctx, cancel := context.WithCancel(context.Background())
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGSTOP, syscall.SIGUSR1)
go func(chan os.Signal) {
select {
case <-ch:
cancel()
fmt.Println("receive signal")
}
}(ch)
var wg sync.WaitGroup
wg.Add(1)
go watch(ctx, &wg)
fmt.Println("waiting")
wg.Wait()
fmt.Println("finish")
}
func watch(ctx context.Context, wg *sync.WaitGroup) {
defer func() {
wg.Done()
}()
for {
select {
case <-ctx.Done():
fmt.Println("stop watch")
return
default:
fmt.Println("watching ")
}
time.Sleep(1 * time.Second)
}
}