为什么要用Context(上下文)

写在前面

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

New Context

WithCancel

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

返回一个携带一个新的Done管道的parent副本,parentcancel被执行或parentDoneclose的时候,副本的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)

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

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

  • Contextcancel的时候,关联的资源也会被释放
  • 不要在struct中存储Context实例,明确地通过函数参数传递,并且一般作为第一个参数
  • 即使函数运行,也不要传递nil Context,如果不确定传递的Context将来有什么用处,可以传递一个context.TODO
  • 通过Context携带参数一般只用于请求域数据,可选参数应该明确通过函数参数传递
  • 同一个Context可运行于不同Goroutines中的函数,Context可以在多个Goroutines间同步
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容