前言
关于 context 的源码分析网上已经有很多优秀的文章了,本文主要记录一下 context 的使用注意事项和技巧,还有 context 为什是并发安全的。
context 使用注意事项
资源未释放
ctx, _ := context.WithTimeout(context.TODO(), 1*time.Second)
在编写代码的过程当我们申请了一个资源应该养成释放这个资源的习惯,相比 c++ 语言在析构函数里面释放资源,Go 通常是借助 defer 语义
ctx, cancel := context.WithTimeout(context.TODO(), 1*time.Second)
defer cancel()
context value 被覆盖
我们通常使用以下语法,使用字符串 key 设置 context value,在多人维护一个项目或者调用其他函数的时候有可能因为定义了同样的 key 导致 value 被覆盖,从而产生意想不到的问题
ctx = context.WithValue(ctx, "key", "value")
参照 context 包我们可以发现,他是通过自定义类型避免参数被覆盖问题,代码如下,在设置 value 的时候自定义了一个 cancelCtxKey 类型
var cancelCtxKey int
p, ok := parent.Value(&cancelCtxKey).(*cancelCtx)
if !ok {
return nil, false
}
并发安全特性
context 是并发安全的,当 ctx 在多个协程之间传播并进行 WithValue 时是怎么保证并发安全的呢,答案是 COW(写时复制),接下来我们看一下 WithValue 实现的源码
func WithValue(parent Context, key, val any) Context {
if parent == nil {
panic("cannot create context from nil parent")
}
if key == nil {
panic("nil key")
}
if !reflectlite.TypeOf(key).Comparable() {
panic("key is not comparable")
}
return &valueCtx{parent, key, val}
}
从源码可以发现在使用 WithValue 时 重新 new 了一个 valueCtx 对象,因此在不同的协程里面使用的是不同的副本,所以就不会有并发问题,有点 MVCC 的味道了。
Github:wang1309