设为首页 加入收藏

TOP

Go语言 context包源码学习(一)
2023-07-23 13:25:51 】 浏览:111
Tags:语言 context 包源码

你必须非常努力,才能看起来毫不费力!

微信搜索公众号[ 漫漫Coding路 ],一起From Zero To Hero !

前言

日常 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 接口比较简单,定义了四个方法:

  • Deadline() 方法返回两个值,deadline 表示 Context 将会在什么时间点取消,ok 表示是否设置了deadline。当 ok=false 时,表示没有设置deadline,那么此时 deadline 将会是个零值。多次调用这个方法返回同样的结果。
  • Done() 返回一个只读的 channel,类型为 chan struct{},如果当前的 Context 不支持取消,Done 返回 nil。我们知道,如果一个 channel 中没有数据,读取数据会阻塞;而如果channel被关闭,则可以读取到数据,因此可以监听 Done 返回的 channel,来获取 Context 取消的信号。
  • Err() 返回 Done 返回的 channel 被关闭的原因。当 channel 未被关闭时,Err() 返回 nil;channel 被关闭时则返回相应的值,比如 Canceled 、DeadlineExceeded。Err() 返回一个非 nil 值之后,后面再次调用会返回相同的值。
  • Value() 返回 Context 保存的键值对中,key 对应的 value,如果 key 不存在则返回 nil。

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

 func Stream(ctx context.Context, out chan<- Value) error {
 	for {
    
    // 处理数据
 		v, err := DoSomething(ctx)
 		if err != nil {
 			return err
 		}
    
    // ctx.Done() 读取到数据,说明获取到了任务取消的信号
 		select {
 		case <-ctx.Done():
 			return ctx.Err()
    // 否则将结果输出,继续计算
 		case out <- v:
 		}
 	}
 }

Value() 也是一个比较常用的方法,用于在上下文中传递一些数据。使用 context.WithValue() 方法存

首页 上一页 1 2 3 4 5 6 下一页 尾页 1/6/6
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇为什么说 Go 语言字符串是不可变.. 下一篇[grpc快速入门] 一 grpc生成与调用

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目