Go: Context 理解

在看proxy的时候,看到了context这个package.

source 1: Package context

Incoming requests to a server should create a Context, 

and outgoing calls to servers should accept a Context

> * 对于,访问本服务器的Incoming 请求应该建立一个Context.

> * 对于,从本服务器访问其他服务器的Outgoing 请求应该接收Context.

下面的链接中包含了创建一个context的代码。

source 2: Go Concurrency Patterns: Context

WithCancel
WithDeadline
WithTimeout
WithValue

以上函数根据不同的含义,分别返回parent Context的child Context。

package main

import (
    "context"
    "fmt"
    "time"
)

func main() {
    d := time.Now().Add(50 * time.Millisecond)
    ctx, cancel := context.WithDeadline(context.Background(), d)

    // Even though ctx will be expired, it is good practice to call its
    // cancelation function in any case. Failure to do so may keep the
    // context and its parent alive longer than necessary.
    defer cancel()

    select {
    case <-time.After(1 * time.Second):
        fmt.Println("overslept")
    case <-ctx.Done():
        fmt.Println(ctx.Err())
    }

}

总结:Context是Go提供的一种机制,可以完成:

场景1:有一个请求,其中包含了若干子请求。当子请求还是与对应的服务器通信时,父请求因为各种各样的原因停止了,

此时,单纯的关闭父请求是没有意义的,Context机制就可以实现关闭父请求的同时,关闭其他的子请求。

场景2:传递 request-scoped 数据。传递动态上下文所需的数据。

示例如下:

package main

import (
    "context"
    "fmt"
    "time"
)

type key int

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    t := key(1)
    ctx_child := context.WithValue(ctx, t, "hello")
    go test(ctx_child)
    cancel()
    time.Sleep(1 * time.Second)
}

func test(ctx context.Context) {
    t := key(1)
    fmt.Printf("%s \n", ctx.Value(t))
    select {
    case <-ctx.Done():
        fmt.Printf("bye bye in child \n")
    }
}

output:
hello
bye bye in child

上面的程序刻意创造了两层结构,验证了parent ctx关闭时,child ctx也关闭,同时也实现了 scope传值。

ctx在使用中,官方推荐的是一层层深入,每一层都需要创建新的ctx,传递给新的go程,同时在本层中关闭。

调用必须是同步的调用。原因是上层的context调用,会关闭下层所有的 context的调用,则下层的各个功能模块还没有完成任务,而中途关闭。

反过来说,当上层出现问题的时候,这个作用刚好可以保证所有的下层都正常关闭。

参考:go-context使用

正确的调用:

  1层
  生成child_ctx
  defer child_cancel()
    2层调用(ctx)
        起go程跑任务
        select {
            case: ctx.Done()  //捕捉上层的异常,或者ctx本身关闭
            case: 任务完成
        }
    2层结束
  1层结束
  
错误的调用:

  1层
    2层调用
        起Go程去跑任务
    2层结束
  1层结束

这时候Go程还在跑,结果还没有出来,就被上层强制关闭了,逗。