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")}
Context就是一个上下文结构体,通过这个结构体能够做一些事情,例如 并发控制goroutine 数据存储等;类似于下面这个图上的结构。
首先定义了context 接口,规定了context的基本能力
然后为了实现不同的能力,进行了各种实现。如 valueCtx cancelCtx timeCtx等。
首先看一下源码里面的第一个实现 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()}