写在前面
相比在参数中传递用于控制流程的自定义管道变量,Context
可以更方便地串联、管理多个Goroutine
,在大型项目中发挥着重要的作用。
New Context
WithCancel
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
返回一个携带一个新的Done
管道的parent
副本,parent
的cancel
被执行或parent
的Done
被close
的时候,副本的Done
会被close
。
func main() {
stoping := make(chan bool)
ctx, cancel := context.WithCancel(context.Background())
go check(ctx, stoping)
time.Sleep(3 * time.Second)
fmt.Println("after 3s")
cancel()
<-stoping
}
func check(ctx context.Context, stoping chan bool) {
fmt.Println("[check] start...")
<-ctx.Done()
fmt.Println("[check] end...")
stoping <- true
}
输出:
[check] start...
after 3s
[check] end...
WithDeadline
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
副本deadline
或cancel
的时候,或parent
的Done
被close
的时候,副本的Done
将close
WithTimeout
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
返回WithDeadline(parent, time.Now().Add(timeout))
。
func main() {
ctx, _ := context.WithTimeout(context.Background(), 2*time.Second)
go check(ctx, 1*time.Second)
select {
case <-ctx.Done():
fmt.Println("[main] ", ctx.Err())
}
}
func check(ctx context.Context, timeout time.Duration) {
select {
case <-ctx.Done():
fmt.Println("[check] ", ctx.Err())
case <-time.After(timeout):
fmt.Println("[check] timeout")
}
}
输出:
[check] timeout
[main] context deadline exceeded
WithValue
func WithValue(parent Context, key interface{}, val interface{}) Context
func main() {
stoping := make(chan bool)
ctx := context.WithValue(context.Background(), "name", "Jack")
go check(ctx, stoping)
<-stoping
}
func check(ctx context.Context, stoping chan bool) {
fmt.Println("[check] Hi", ctx.Value("name"))
stoping <- true
}
输出:
[check] Hi Jack
Background
Background returns a non-nil, empty Context. It is never canceled, has no values, and has no deadline
。这个一般在main
函数、初始化、测试和顶层使用,然后通过它往后派生。
TODO
几乎是Background
的别名般的存在,但相比Background
,一些静态分析工具可以通过TODO
分析Context
有没有正确地传递。一般在对Context
的使用还不清晰的地方使用。
Note
-
Context
被cancel
的时候,关联的资源也会被释放 - 不要在
struct
中存储Context
实例,明确地通过函数参数传递,并且一般作为第一个参数 - 即使函数运行,也不要传递
nil Context
,如果不确定传递的Context
将来有什么用处,可以传递一个context.TODO
- 通过
Context
携带参数一般只用于请求域数据,可选参数应该明确通过函数参数传递 - 同一个
Context
可运行于不同Goroutines
中的函数,Context
可以在多个Goroutines
间同步