Tommy

Tommy

写代码是热爱,但也是生活 !
github

[Golang]context引发的一个小bug

先看代码:

func AsyncAdd(run func() error)  {
	//TODO: 扔进异步协程池
	go run()
}

func GetInstance(ctx context.Context,id uint64) (string, error) {
	data,err := GetFromRedis(ctx,id)
	if err != nil && err != redis.Nil{
		return "", err
	}
	// 没有找到数据
	if err == redis.Nil {
		data,err = GetFromDB(ctx,id)
		if err != nil{
			return "", err
		}
		AsyncAdd(func() error{
			return UpdateCache(ctx,id,data)
		})
	}
	return data,nil
}

func GetFromRedis(ctx context.Context,id uint64) (string,error) {
	// TODO: 从redis获取信息
	return "",nil
}

func GetFromDB(ctx context.Context,id uint64) (string,error) {
	// TODO: 从DB中获取信息
	return "",nil
}

func UpdateCache(ctx context.Context,id interface{},data string) error {
	// TODO:更新缓存信息
	return nil
}

func main()  {
	ctx,cancel := context.WithTimeout(context.Background(), 3 * time.Second)
	defer cancel()
	_,err := GetInstance(ctx,2021)
	if err != nil{
		return
	}
}

原因:因为误用 导致异步更新失败

分析#

我们先简单分析一下,这一段代码要干什么?其实很简单,我们想要获取一段信息,首先会从缓存中获取,如果缓存中获取不到,我们就从 DB 中获取,从 DB 中获取到信息后,在协程池中放入更新缓存的方法,异步去更新缓存。整个设计是不是很完美,但是在实际工作中,异步更新缓存就没有成功过。。。。

导致失败的原因就在这一段代码:

AsyncAdd(func() error{
			return UpdateCache(ctx,id,data)
		})

错误的原因只有一个,就是这个 ctx,如果改成这样,就啥事没有了。

AsyncAdd(func() error{
			ctxAsync,cancel := context.WithTimeout(context.Background(),3 * time.Second)
			defer cancel()
			return UpdateCache(ctxAsync,id,data)
		})

原因#

在原始的代码中,UpdateCache 函数被调用时使用的是外部的上下文对象 ctx,这样会导致 UpdateCache 函数的超时时间受到了 GetInstance 函数传入的上下文对象的影响。

通过在匿名函数中创建一个新的上下文对象 ctxAsync 并将其传递给 UpdateCache 函数,可以确保 UpdateCache 函数的超时时间独立于外部的上下文对象。

同时,由于 ctxAsync 是在匿名函数内部创建的,所以需要在函数执行结束时调用 cancel () 函数来释放相关资源。

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。