Go Context
Context 介绍
用于在Goroutine之间传递请求作用域数据、取消信号和超时时间。从而实现更加灵活可靠的并发编程。如果上层Goroutine失败后,可以通过信号的同步取消下层的其余操作,避免对计算资源的浪费。
Context 类型
根Context
-
context.Background()
返回非 nil 的空context值,是初始化 context 的默认值,还不具备 context 的其他功能,用作传入请求的顶级上下文,所有其他的上下文都应该从它衍生出来。
-
context.TODO()
同 background ,底层源码皆是通过
new(emptyCtx)
语句初始化,初始化过程中接口均实现的是空方法,并不具备其他功能。区别是TODO应该仅在不确定应该使用哪种上下文时使用。
衍生Context
-
WithCancel(parent Context) (ctx Context, cancel CancelFunc)
接受一个父
context.Context
作为参数,并返回一个新的context.Context
和一个CancelFunc
函数。新的上下文会继承父上下文的属性,但也可以通过调用CancelFunc
来主动取消上下文。当调用CancelFunc
时,与该上下文相关的所有子上下文也会被取消。1package main 2 3import ( 4 "context" 5 "fmt" 6) 7 8func main() { 9 gen := func(ctx context.Context) <-chan int { 10 dst := make(chan int) 11 n := 1 12 go func() { 13 for { 14 select { 15 case <-ctx.Done(): 16 return 17 case dst <- n: 18 n++ 19 } 20 } 21 }() 22 return dst 23 } 24 25 ctx, cancel := context.WithCancel(context.Background()) 26 defer cancel() 27 // cancel 是幂等函数,只有第一次调用会触发取消操作,后续的调用不会产生任何额外的效果。 28 29 for n := range gen(ctx) { 30 fmt.Println(n) 31 if n == 5 { 32 break 33 } 34 } 35}
-
WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
接受一个父
context.Context
和一个截止时间d
作为参数,并返回一个新的context.Context
。这个新的上下文会在指定的截止时间之前被取消。当截止时间到达时,与此上下文相关的Done
通道将被关闭。1package main 2 3import ( 4 "context" 5 "fmt" 6 "time" 7) 8 9const shortDuration = 1 * time.Millisecond 10 11func main() { 12 d := time.Now().Add(shortDuration) 13 ctx, cancel := context.WithDeadline(context.Background(), d) 14 15 defer cancel() 16 17 select { 18 case <-time.After(1 * time.Second): 19 fmt.Println("overslept") 20 case <-ctx.Done(): 21 fmt.Println(ctx.Err()) 22 } 23 24} 25 26// 在WithCancel的基础上,增加了deadline,是一个时间值。当时间超过了截止日期后会调用 context.timerCtx.cancel 同步取消信号。
-
WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
接受一个父
context.Context
和一个超时时间timeout
作为参数,并返回一个新的context.Context
。这个新的上下文会在指定的超时时间过去之后被取消。超时时间可以是一个持续时间间隔(time.Duration
)。1package main 2 3import ( 4 "context" 5 "fmt" 6 "time" 7) 8 9const shortDuration = 1 * time.Millisecond 10 11func main() { 12 ctx, cancel := context.WithTimeout(context.Background(), shortDuration) 13 defer cancel() 14 15 select { 16 case <-time.After(1 * time.Second): 17 fmt.Println("overslept") 18 case <-ctx.Done(): 19 fmt.Println(ctx.Err()) // prints "context deadline exceeded" 20 } 21} 22 23// 与WithDeadline类似,只不过传入的是duration,而不是具体的时间值。底层仍然调用的是WithDeadline 24// 并通过time.Now().Add(timeout)将duration转为具体的时间值
-
WithValue(parent Context, key, val any) Context
接受一个父
context.Context
、一个键key
和一个值val
作为参数,并返回一个新的context.Context
。新的上下文会继承父上下文的属性,并将键值对存储在上下文中。这样,在整个上下文树中,可以使用context.Value
方法根据键访问对应的值。1package main 2 3import ( 4 "context" 5 "fmt" 6) 7 8func main() { 9 type favContextKey string 10 11 f := func(ctx context.Context, k favContextKey) { 12 if v := ctx.Value(k); v != nil { 13 fmt.Println("found value:", v) 14 return 15 } 16 fmt.Println("key not found:", k) 17 } 18 19 k := favContextKey("language") 20 ctx := context.WithValue(context.Background(), k, "Go") 21 22 f(ctx, k) 23 f(ctx, favContextKey("color")) 24 25}
使用建议
- 同一个 context 可以传递到多个 goroutine 中,并且,context 是
并发安全
的。 - 关键参数不应传入Context中,而应该显式的声明在函数中,从而提高代码的可读性。
- context一般作为函数的第一个参数传入,并命名为
ctx
。 - 及时取消Context。当不再需要上下文时,调用其
cancel
函数以避免资源泄漏。