Context使用

包context定义了Context类型,它携带跨API边界和进程之间的截止日期、取消信号和其他请求范围的值。

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

type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key any) any
}
  1. Deadline — 返回 context.Context 被取消的时间,也就是完成工作的截止日期;

// 示例:获取上下文的截止时间
// 返回一个截止时间,如果设置了截止时间,则 ok 为 true,否则为 false
func checkDeadline(ctx context.Context) {
    deadline, ok := ctx.Deadline()
    if ok {
        fmt.Printf("截止时间: %v\n", deadline)
    } else {
        fmt.Println("没有设置截止时间")
    }
}
  1. Done — 返回一个 Channel,这个 Channel 会在当前工作完成或者上下文被取消后关闭,多次调用 Done 方法会返回同一个 Channel;

  1. Err — 返回 context.Context 结束的原因,它只会在 Done 方法对应的 Channel 关闭时返回非空的值;

    1. 如果 context.Context 被取消,会返回 Canceled 错误;

    2. 如果 context.Context 超时,会返回 DeadlineExceeded 错误;

  1. Value — 从 context.Context 中获取键对应的值,对于同一个上下文来说,多次调用 Value 并传入相同的 Key 会返回相同的结果,该方法可以用来传递请求特定的数据;

1 解决问题

  • 请求级别的数据传递

  • 超时控制

  • 取消信号传播

  • 跨 API 边界的数据传递

  • goroutine 生命周期管理

2 设计原理

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

每一个 context.Context 都会从最顶层的 Goroutine 一层一层传递到最下层。context.Context 可以在上层 Goroutine 执行出现错误时,将信号及时同步给下层。例如:

context.Context 的使用方法和设计原理 — 多个 Goroutine 同时订阅 ctx.Done() 管道中的消息,一旦接收到取消信号就立刻停止当前正在执行的工作。

3 上下文使用

3.1 默认上下文

context 包中最常用的方法还是 context.Backgroundcontext.TODO,这两个方法都会返回预先初始化好的私有变量 backgroundtodo,它们会在同一个 Go 程序中被复用:

这两个结构体里面包含的都是emptyCtx空结构体。

从上述代码中,我们不难发现 context.emptyCtx、 通过空方法实现了 context.Context 接口中的所有方法,它没有任何功能。

从源代码来看,context.Background 和 context.TODO 也只是互为别名,没有太大的差别,只是在使用和语义上稍有不同:

  • context.Background 是上下文的默认值,所有其他的上下文都应该从它衍生出来;

  • context.TODO 应该仅在不确定应该使用哪种上下文时使用;

在多数情况下,如果当前函数没有上下文作为入参,我们都会使用 context.Background 作为起始的上下文向下传递。

3.2 取消信号

context.WithCancel 函数能够从 context.Context 中衍生出一个新的子上下文并返回用于取消该上下文的函数。一旦我们执行返回的取消函数,当前上下文以及它的子上下文都会被取消,所有的 Goroutine 都会同步收到这一取消信号。

示例:

3.3 超时时间

设置超时时间的ctx有两种WithDeadlineWithTimeout

这两个上下文的区别是一个设置的具体日期,一个设置持续时间。

3.4 值传递

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

最后更新于