深度解析:Golang Context 的实现原理全览

发表时间: 2024-01-18 10:10
  1. 常用方法

context在golang开发中使用的非常广泛,简单总结一下context的使用和原理,在这里分享一下。我们通常会使用context去并发控制goroutine生命周期、数据存储传递。下面是一个常见用法。

ctx := context.Background()valueCtx := context.WithValue(ctx, "name", "wq") // context嵌套和数据存储cancelCtx, f := context.WithCancel(valueCtx) // cancelContextdefer f()select {  case <-cancelCtx.Done():  fmt.Println("done")}timeCtx, f2 := context.WithTimeout(cancelCtx, time.Second) // timeContextdefer f2()select {  case <-timeCtx.Done():  fmt.Println("done")}
  1. 整体分析

Context就是一个上下文结构体,通过这个结构体能够做一些事情,例如 并发控制goroutine 数据存储等;类似于下面这个图上的结构。


context的各种实现

首先定义了context 接口,规定了context的基本能力

  • Deadline:context 的过期时间;
  • Done:context 中的 channel;
  • Err:返回错误;
  • Value:context 中的对应 key 的值.

然后为了实现不同的能力,进行了各种实现。如 valueCtx cancelCtx timeCtx等。

  1. 各种实现分析

首先看一下源码里面的第一个实现 emptyCtx, 是一个空的 context,本质上类型为一个整型。我用常用的 context.Background() 和 context.TODO()都是这个实例,本质上没有什么功能实现。

type emptyCtx intfunc (*emptyCtx) Deadline() (deadline time.Time, ok bool) {    return}func (*emptyCtx) Done() <-chan struct{} {    return nil}func (*emptyCtx) Err() error {    return nil}func (*emptyCtx) Value(key any) any {    return }

然后再看一下 valueCtx, 源码里面的定义就是下面的

type valueCtx struct {    Context    key, val any}

可以看出来 valueCtx 是在 Context基础上封装了一下,实现了数据存储的功能。而且每个valueCtx只能存储一个kv对。

下面我们在看一下cancelCtx, 可以发现添加了锁和子类保存

type cancelCtx struct {    Context       mu       sync.Mutex            // protects following fields    done     atomic.Value          // of chan struct{}, created lazily, closed by first cancel call    children map[canceler]struct{} // set to nil by the first cancel call    err      error                 // set to non-nil by the first cancel call}

我们经常使用就是下面这种,底层的实现原理就是通过调用 cancel 方法关闭channel通道,进行通知监听 cancelCtx 的多路复用。

cancelCtx, f := context.WithCancel(valueCtx)defer f() // cancel() 方法会close channelselect {case <-cancelCtx.Done(): // 监听到 ch<-struct{}   fmt.Println("done")}

而 timeCtx 就是对于cancelCtx的增加功能,增加了定时触发取消的功能,通过看结构体也可以发现

type timerCtx struct {    cancelCtx    timer *time.Timer // Under cancelCtx.mu.    deadline time.Time}

这些就是对于golang中context的简单总结了,实际工作使用中是非常广泛的。下面一个就是一个通过 Inject 注入 ctx数据,然后通过 Extract 获取到数据的例子。不用在意具体的功能,简单看一下具体实现ctx传递的步骤就可以了。

func main() {    // 获取 TextMapPropagator    propagator := otel.GetTextMapPropagator()    // 创建一个 Context,并添加一些信息    ctx := context.Background()    ctx = otel.GetTracerProvider().GetTracer("example").Start(ctx, "request")    // 将上下文信息注入到 HTTP 请求头中    req := http.NewRequest("GET", "https://example.com", nil)    carrier := propagation.HeaderCarrier(req.Header)    propagator.Inject(ctx, carrier)    // 发送 HTTP 请求到其他服务    // 在接收方,从 HTTP 请求头中提取上下文信息    receivedCtx := context.Background()    extractedCarrier := propagation.HeaderCarrier(req.Header)    propagator.Extract(receivedCtx, extractedCarrier)    // 在接收方,在新的 Context 中获取上下文信息    span := otel.GetTracerProvider().GetTracer("example").Start(receivedCtx, "response")    defer span.End()}