go语言-上下文 Context

文章目录

    • go语言-context
      • 设计原理

go语言-context

上下文 Context
参考URL: https://draveness.me/golang/docs/part3-runtime/ch06-concurrency/golang-context/

context.Context 是 Go 语言在 1.7 版本中引入标准库的接口,该接口定义了四个需要实现的方法,其中包括:

  1. Deadline — 返回 context.Context 被取消的时间,也就是完成工作的截止日期;
  2. Done — 返回一个 Channel,这个 Channel 会在当前工作完成或者上下文被取消之后关闭,多次调用 Done 方法会返回同一个 Channel;
  3. Err — 返回 context.Context 结束的原因,它只会在 Done 返回的 Channel 被关闭时才会返回非空的值;
  • 如果 context.Context 被取消,会返回 Canceled 错误;
  • 如果 context.Context 超时,会返回 DeadlineExceeded 错误;
  1. Value — 从 context.Context 中获取键对应的值,对于同一个上下文来说,多次调用 Value 并传入相同的 Key 会返回相同的结果,该方法可以用来传递请求特定的数据;

设计原理

在 Goroutine 构成的树形结构中对信号进行同步以减少计算资源的浪费是 context.Context 的最大作用。Go 服务的每一个请求的都是通过单独的 Goroutine 处理的,HTTP/RPC 请求的处理器会启动新的 Goroutine 访问数据库和其他服务。

如下图所示,我们可能会创建多个 Goroutine 来处理一次请求,而 context.Context 的作用就是在不同 Goroutine 之间同步请求特定数据、取消信号以及处理请求的截止日期。

  • Context 与 Goroutine 树
    go语言-上下文 Context_第1张图片
    每一个 context.Context 都会从最顶层的 Goroutine 一层一层传递到最下层。context.Context 可以在上层 Goroutine 执行出现错误时,将信号及时同步给下层。

  • 不使用 Context 同步信号
    go语言-上下文 Context_第2张图片
    如上图所示,当最上层的 Goroutine 因为某些原因执行失败时,下层的 Goroutine 由于没有接收到这个信号所以会继续工作;但是当我们正确地使用 context.Context 时,就可以在下层及时停掉无用的工作以减少额外资源的消耗。

  • 使用 Context 同步信号
    go语言-上下文 Context_第3张图片我们可以通过一个代码片段了解 context.Context 是如何对信号进行同步的。在这段代码中,我们创建了一个过期时间为 1s 的上下文,并向上下文传入 handle 函数,该方法会使用 500ms 的时间处理传入的『请求』:

    func main() {
    	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
    	defer cancel()
    
    	go handle(ctx, 500*time.Millisecond)
    	select {
    	case <-ctx.Done():
    		fmt.Println("main", ctx.Err())
    	}
    }
    
    func handle(ctx context.Context, duration time.Duration) {
    	select {
    	case <-ctx.Done():
    		fmt.Println("handle", ctx.Err())
    	case <-time.After(duration):
    		fmt.Println("process request with", duration)
    	}
    }
    

    因为过期时间大于处理时间,所以我们有足够的时间处理该『请求』,运行上述代码会打印出如下所示的内容:

    $ go run context.go
    process request with 500ms
    main context deadline exceeded
    

    handle 函数没有进入超时的 select 分支,但是 main 函数的 select 却会等待 context.Context 的超时并打印出 main context deadline exceeded。

    如果我们将处理『请求』时间增加至 1500ms,整个程序都会因为上下文的过期而被中止,:

    $ go run context.go
    main context deadline exceeded
    handle context deadline exceeded
    

    相信这两个例子能够帮助各位读者理解 context.Context 的使用方法和设计原理 — 多个 Goroutine 同时订阅 ctx.Done() 管道中的消息,一旦接收到取消信号就立刻停止当前正在执行的工作。

总结: Go 语言中的 context.Context 的主要作用还是在多个 Goroutine 组成的树中同步取消信号以减少对资源的消耗和占用, 虽然它也有传值的功能,但是这个功能我们还是很少用到。

在真正使用传值的功能时我们也应该非常谨慎,使用 context.Context 进行传递参数请求的所有参数一种非常差的设计,比较常见的使用场景是传递请求对应用户的认证令牌以及用于进行分布式追踪的请求 ID。

你可能感兴趣的:(Go,go)