Go Context 简介

2,453 阅读2分钟

这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战

Context

上下文 context.Context Go 语言中用来设置截止日期,同步信号,传递请求相关的结构体,context 与 gotoutine 有比较密切的关系,是 Go 语言中的独特设计。 在这里插入图片描述

Context 作用

  • 每个 Context 都会从最顶层 Goroutine 一层一层传递到最下层,context.Context 可以在上层 Goroutine 执行出现错误,会将信号及时同步到下一次层,这样,上层因为某些原因失败时, 下层就可以停掉无用的工作,以减少资源损耗。实际应用: RPC 超时时间设置
  • context 中一般意义 context.WithValue 能从父上下文中创建一个子上下文,传值的子上下文使用 context.valueCtx 类型。WithValue 是一对 kv 类型,可用来传值,实际应用:传递全局唯一的调用链

Context 接口

Context 是 Go 语言再 1.7 版本引入的标准库接口,有以下需要实现的方法

  1. Deadlime 返回 context.Context 被取消的时间,也就是完成工作的截止时间
  2. Done 返回一个 Channel ,这个 Channel 会在当前工作完成或者被取消后关闭,多次调用 Done 方法会返回同一个Channle
  3. Err 返回 context.Context 结束的原因,只会在 Done 方法对应的 Channel 关闭时返回非空的值
  4. Value 从 context.Context 中获取对应的值,对同一个上下文来说,多次调用 Value 并传入相同的 Key 会返回相同的结果。改方法可以用来传递特定的请求。
  • 如果 context.Context 被取消,会返回 Canceled 错误;
  • 如果 context.Context 超时,会返回 DeadlineExceeded 错误;
type Context interface {
	Deadline() (deadline time.Time, ok bool)
	Done() <-chan struct{}
	Err() error
	Value(key interface{}) interface{}
}

在这里插入图片描述

使用 context 同步信号

创建一个过期时间为 1s 的上下文, 并向上下文传入 handle 函数,该方法会使用 500ms 的时间处理传入的请求。

package contexttest

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

func TestContext(t *testing.T) {
	// 创建一个过期时间为1s的上下文
	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)
	}
}

运行结果:

=== RUN   TestContext
process request with 500ms
main context deadline exceeded
--- PASS: TestContext (1.00s)
PASS

Context 创建

  • 根 Context: 通过 context.Backgroud() 创建
  • 子 Conetxt: context.WithCancel(parentConetxt) 创建
ctx, cancel := context.WithCancel(conetext.Background())
  • 当前 Context 被取消时,其他的子 context 都会被取消
  • 接收取消通知 <- ctx.Done()

测试代码package main

import (
	"context"
	"testing"
	"time"
)

func isCanceled(ctx context.Context) bool {
	select {
	case <-ctx.Done():
		return true
	default:
		return false
	}
}

func TestCanceledByConetxt(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	for i := 0; i < 5; i++ {
		go func(i int, ctx context.Context) {
			for {
				if isCanceled(ctx) {
					break
				}
			}
			time.Sleep(time.Second * 1)
		}(i, ctx)
	}
	cancel()
}