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
时,与该上下文相关的所有子上下文也会被取消。package main import ( "context" "fmt" ) func main() { gen := func(ctx context.Context) <-chan int { dst := make(chan int) n := 1 go func() { for { select { case <-ctx.Done(): return case dst <- n: n++ } } }() return dst } ctx, cancel := context.WithCancel(context.Background()) defer cancel() // cancel 是幂等函数,只有第一次调用会触发取消操作,后续的调用不会产生任何额外的效果。 for n := range gen(ctx) { fmt.Println(n) if n == 5 { break } } }
WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
接受一个父
context.Context
和一个截止时间d
作为参数,并返回一个新的context.Context
。这个新的上下文会在指定的截止时间之前被取消。当截止时间到达时,与此上下文相关的Done
通道将被关闭。package main import ( "context" "fmt" "time" ) const shortDuration = 1 * time.Millisecond func main() { d := time.Now().Add(shortDuration) ctx, cancel := context.WithDeadline(context.Background(), d) defer cancel() select { case <-time.After(1 * time.Second): fmt.Println("overslept") case <-ctx.Done(): fmt.Println(ctx.Err()) } } // 在WithCancel的基础上,增加了deadline,是一个时间值。当时间超过了截止日期后会调用 context.timerCtx.cancel 同步取消信号。
WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
接受一个父
context.Context
和一个超时时间timeout
作为参数,并返回一个新的context.Context
。这个新的上下文会在指定的超时时间过去之后被取消。超时时间可以是一个持续时间间隔(time.Duration
)。package main import ( "context" "fmt" "time" ) const shortDuration = 1 * time.Millisecond func main() { ctx, cancel := context.WithTimeout(context.Background(), shortDuration) defer cancel() select { case <-time.After(1 * time.Second): fmt.Println("overslept") case <-ctx.Done(): fmt.Println(ctx.Err()) // prints "context deadline exceeded" } } // 与WithDeadline类似,只不过传入的是duration,而不是具体的时间值。底层仍然调用的是WithDeadline // 并通过time.Now().Add(timeout)将duration转为具体的时间值
WithValue(parent Context, key, val any) Context
接受一个父
context.Context
、一个键key
和一个值val
作为参数,并返回一个新的context.Context
。新的上下文会继承父上下文的属性,并将键值对存储在上下文中。这样,在整个上下文树中,可以使用context.Value
方法根据键访问对应的值。package main import ( "context" "fmt" ) func main() { type favContextKey string f := func(ctx context.Context, k favContextKey) { if v := ctx.Value(k); v != nil { fmt.Println("found value:", v) return } fmt.Println("key not found:", k) } k := favContextKey("language") ctx := context.WithValue(context.Background(), k, "Go") f(ctx, k) f(ctx, favContextKey("color")) }
使用建议
- 同一个 context 可以传递到多个 goroutine 中,并且,context 是
并发安全
的。 - 关键参数不应传入Context中,而应该显式的声明在函数中,从而提高代码的可读性。
- context一般作为函数的第一个参数传入,并命名为
ctx
。 - 及时取消Context。当不再需要上下文时,调用其
cancel
函数以避免资源泄漏。