[Golang]context引发的一个小bug

先看代码：

Copy
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 中获取到信息后，在协程池中放入更新缓存的方法，异步去更新缓存。整个设计是不是很完美，但是在实际工作中，异步更新缓存就没有成功过。。。。

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

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

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

Copy
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 () 函数来释放相关资源。