go语言Context的用法
在写web项目的时候经常需要知道一段逻辑的上下文信息,比如一个调用联里,很多地方都需要知道当前用户的信息,如果每个函数都传递一个用户参数,显得非常不优雅,而且会导致函数的参数过多,可读性非常差。
在java中,可以使用ThreadLocal来存储整个调用链(单机单线程)需要共享的数据,在go语言中,Context承担了类似ThreadLocal的角色,并且
这两种能力是ThreadLocal不具备的。
Context
创建Context
创建Context的两种方法:
这两种方式实际上效果都一样,但是有个约定俗成的规范,就是如果你不知道这个Context的用途的情况下,使用context.Background(),反之则用context.TODO()。
还有一个约定就是,Context一般都做函数的第一个参数。
Context存储信息
context包有一个withValue函数用于存储信息到Context里
# parent 要存储信息的ctx
# key 用于检索信息的key
# value 需要存放在上下文里的信息
func WithValue(parent Context, key, val any) Context
例子🌰:
func doSomething(ctx context.Context) {
fmt.Println("Doing something!")
fmt.Printf("%s\n", ctx.Value("key"))
}
func main() {
ctx := context.Background()
ctx = context.WithValue(ctx, "key", "value")
doSomething(ctx)
}
Context嵌套和包装
Context中所有存储的值都是immutable值,是不可以修改的,因为context.WithValue函数会返回一个新的Context,新的Context内部包含老的Context和新设置的值。所以每次对Context操作,无论是新增还是修改key,都是针对Context做了一层包装。老的value并不会丢失,可以通过老ctx变量检索到。
是不是有点类似docker的layer?
例子🌰:
func doSomething2(ctx context.Context) {
fmt.Printf("do something2 -- ctx-key: %s \n", ctx.Value("key"))
}
func doSomething1(ctx context.Context) {
fmt.Printf("do something1 -- ctx-key: %s \n", ctx.Value("key"))
newCtx := context.WithValue(ctx, "key", "value2")
doSomething2(newCtx)
fmt.Printf("do something1 -- ctx-key: %s \n", ctx.Value("key"))
}
func main() {
ctx := context.Background()
ctx = context.WithValue(ctx, "key", "value")
doSomething1(ctx)
}
Context的取消和通知机制
Context的Done()函数会返回一个channel,当Context取消的时候会关闭这个channel,select这个channel就可以在Context取消的时候收到通知。
Context有3种方式可以触发这个通知:
最常用的就是使用WithCancel函数来创建一个可以取消的Context和一个取消函数(CancelFunc),调用这个取消函数就可以取消这个上下文。
例子🌰:
func doSomething2(ctx context.Context) {
fmt.Println("do something2")
for {
select {
case <-ctx.Done():
fmt.Println("ctx done")
return
default:
}
}
}
func doSomething1(ctx context.Context) {
fmt.Println("do something1")
ctx, cancel := context.WithCancel(ctx)
go doSomething2(ctx)
time.Sleep(3 * time.Second)
cancel()
}
func main() {
ctx := context.Background()
doSomething1(ctx)
}
和WithCancel类似,WithDeadline和WithTimeout函数也可以触发这个通知,但是触发方式除了主动调用CancelFunc,还可以根据传入的时间参数自动触发。