编程语言
首页 > 编程语言> > Go语言 context包源码学习

Go语言 context包源码学习

作者:互联网

前言

日常 Go 开发中,Context 包是用的最多的一个了,几乎所有函数的第一个参数都是 ctx,那么我们为什么要传递 Context 呢,Context 又有哪些用法,底层实现是如何呢?相信你也一定会有探索的欲望,那么就跟着本篇文章,一起来学习吧!

需求一

开发中肯定会调用别的函数,比如 A 调用 B,在调用过程中经常会设置超时时间,比如超过2s 就不等待 B 的结果了,直接返回,那么我们需要怎么做呢?

// 睡眠5s,模拟长时间操作
func FuncB() (interface{}, error) {
	time.Sleep(5 * time.Second)
	return struct{}{}, nil
}

func FuncA() (interface{}, error) {

	var res interface{}
	var err error
	ch := make(chan interface{})

  // 调用FuncB(),将结果保存至 channel 中
	go func() {
		res, err = FuncB()
		ch <- res
	}()

  // 设置一个2s的定时器
	timer := time.NewTimer(2 * time.Second)
  
  // 监测是定时器先结束,还是 FuncB 先返回结果
	select {
    
    // 超时,返回默认值
	case <-timer.C:
		return "default", err
    
    // FuncB 先返回结果,关闭定时器,返回 FuncB 的结果
	case r := <-ch:
		if !timer.Stop() {
			<-timer.C
		}
		return r, err
	}

}

func main() {
	res, err := FuncA()
	fmt.Println(res, err)
}

上面我们的实现,可以实现超过等待时间后,A 不等待 B,但是 B 并没有感受到取消信号,如果 B 是个计算密度型的函数,我们也希望B 感知到取消信号,及时取消计算并返回,减少资源浪费。

另一种情况,如果存在多层调用,比如A 调用 B、C,B 调用 D、E,C调用 E、F,在超过 A 的超时时间后,我们希望取消信号能够一层层的传递下去,后续所有被调用到的函数都能感知到,及时返回。

需求二

在多层调用的时候,A->B->C->D,有些数据需要固定传输,比如 LogID,通过打印相同的 LogID,我们就能够追溯某一次调用,方便问题的排查。如果每次都需要传参的话,未免太麻烦了,我们可以使用 Context 来保存。通过设置一个固定的 Key,打印日志时从中取出 value 作为 LogID。

const LogKey = "LogKey"

// 模拟一个日志打印,每次从 Context 中取出 LogKey 对应的 Value 作为LogID
type Logger struct{}
func (logger *Logger) info(ctx context.Context, msg string) {
	logId, ok := ctx.Value(LogKey).(string)
	if !ok {
		logId = uuid.New().String()
	}
	fmt.Println(logId + " " + msg)
}
var logger Logger

// 日志打印 并 调用 FuncB
func FuncA(ctx context.Context) {
	logger.info(ctx, "FuncA")
	FuncB(ctx)
}

func FuncB(ctx context.Context) {
	logger.info(ctx, "FuncB")
}

// 获取初始化的,带有 LogID 的 Context,一般在程序入口做
func getLogCtx(ctx context.Context) context.Context {
	logId, ok := ctx.Value(LogKey).(string)
	if ok {
		return ctx
	}
	logId = uuid.NewString()
	return context.WithValue(ctx, LogKey, logId)
}

func main() {
	ctx = getLogCtx(context.Background())
	FuncA(ctx)
}

这利用到了本篇文章讲到的 valueCtx,继续往下看,一起来学习 valueCtx 是怎么实现的吧!

Context 接口

type Context interface {

	Deadline() (deadline time.Time, ok bool)

	Done() <-chan struct{}

	Err() error

	Value(key interface{}) interface{}
}

Context 接口比较简单,定义了四个方法:

Done() 是一个比较常用的方法,下面是一个比较经典的流式处理任务的示例:监听 ctx.Done() 是否被关闭来判断任务是否需要取消,需要取消则返回相应的原因;没有取消则将计算的结果写入到 out channel中。

标签:c语言,系列,协变,术语,结构,student,person,参数
来源: