为什么要用Context(上下文)

写在前面

相比在参数中传递用于控制流程的自定义管道变量,Context可以更方便地串联、管理多个Goroutine,在大型项目中发挥着重要的作用。

New Context

WithCancel

1
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)

返回一个携带一个新的Done管道的parent副本,parentcancel被执行或parentDoneclose的时候,副本的Done会被close

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
}

输出:

1
2
3
[check] start...
after 3s
[check] end...

WithDeadline

1
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)

副本deadlinecancel的时候,或parentDoneclose的时候,副本的Doneclose

WithTimeout

1
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

返回WithDeadline(parent, time.Now().Add(timeout))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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")
}
}

输出:

1
2
[check] timeout
[main] context deadline exceeded

WithValue

1
func WithValue(parent Context, key interface{}, val interface{}) Context
1
2
3
4
5
6
7
8
9
10
11
12
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
}

输出:

1
[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

  • Contextcancel的时候,关联的资源也会被释放
  • 不要在struct中存储Context实例,明确地通过函数参数传递,并且一般作为第一个参数
  • 即使函数运行,也不要传递nil Context,如果不确定传递的Context将来有什么用处,可以传递一个context.TODO
  • 通过Context携带参数一般只用于请求域数据,可选参数应该明确通过函数参数传递
  • 同一个Context可运行于不同Goroutines中的函数,Context可以在多个Goroutines间同步
打赏
  • © 2016-2020 留叶
  • Powered by Hexo Theme Ayer
    • PV:
    • UV:

请我喝杯咖啡吧~

支付宝
微信