context简介
context用于在多个goroutine中传递上下文信息,且相同的context传递给运行在不同goroutine中的函数是并发安全的。context包定义的上下文类型,可以使用background、TODO创建一个上下文。也可以使用WithDeadline、WithTimeout、WithCancel或WithValue创建的修改副本替换它。
因为context具有在多个不同goroutine中传递上下文信息的性质,所以其常用于并发控制。
下面是对context包的详细使用:
context的使用
创建一个context
context包提供了两种创建方式:
context.Background()context.TODO()
- 前者是上下文的默认值,所有其他的上下文一般都从它衍生而来。
- 后者是在不确定使用哪种上下文时使用。
所以,大多数情况下,都使用context.Background()来作为上下文进行传递。
这两种方式创建出来的是根context,不具有任何功能。需要根据实际选择context包提供的With系列函数来解决相应的问题。
下面是context包提供的With系列函数:
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key, val interface{}) Context
context的衍生具有树形结构的特点。
- 这四个函数所返回的
context都是基于父级context所衍生的。而其返回的context依然可以作为父节点,衍生出其他子节点。- 如果一个
context节点被取消,那么从其所衍生出来的context子节点都会被取消。
下面是对这些With函数的具体使用介绍:
With系列函数
WithCancel 取消控制
WithCancel的作用是,我们可以通过传递这样的上下文,去控制多个goroutine,通过cancel函数,在任意时刻让这些goroutine取消。
下面是例子:
func main() {
ctx, cancel := context.WithCancel(context.Background())
go task(ctx)
time.Sleep(5 * time.Second)
cancel()
time.Sleep(time.Second)
}
func task(ctx context.Context) {
for range time.Tick(time.Second) {
select {
case <-ctx.Done():
fmt.Println(ctx.Err())
return
default:
fmt.Println("tasking...")
}
}
}
超时控制
一个健壮的程序都是需要设置超时时间的,避免由于服务端长时间响应而消耗资源。所以,一些web框架都会采用WithDeadline和WithTimeOut函数来做超时控制。
WithDeadline和WithTimeout的作用是一样的,只是传递的时间参数不同而已,它们都会在超过传递的时候后,自动取消context。- 需要注意的是,两者也会返回
CancelFunc的函数,即便是被自动取消,也需要在结束后,手动取消一下,避免消耗不必要的资源。
下面是例子:
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
go task(ctx)
time.Sleep(6 * time.Second)
}
func task(ctx context.Context) {
for range time.Tick(time.Second) {
select {
case <-ctx.Done():
fmt.Println(ctx.Err())
return
default:
fmt.Println("tasking...")
}
}
}
WithValue 携带数据
WithValue函数可以返回一个可携带数据的context,可以用于在多个goroutine进行传递。
例如,在日常业务开发中,需要有一个trace_id来串联所有日志,那么就可以使用WithValue来实现。
需要注意的是,通过
WithValue得到的context所携带的数据,是可以传递给从其衍生出来的子节点。简单来说,该context的整棵子树都会携带这个数据。
下面是例子:
func main() {
ctx := context.WithValue(context.Background(), "key", "value")
go task(ctx)
time.Sleep(time.Second)
}
func task(ctx context.Context) {
fmt.Println(ctx.Value("key"))
}
使用WithVlue的注意事项:
- 不建议使用
context携带关键参数,关键参数应该显示的声明出来,而不是隐式处理。context最好是携带签名、trace_id这类值。- 建议
key采用内置类型。这是为了避免context因多个包同时使用context而带来冲突。- 在获取
value时,context会首先从当前ctx中获取,如果没有找到,则会从父级继续查找,直到查找到或者在某个父context中返回nil。context传递的key``value键值对是interface类型,因此在类型断言时,要考虑程序的健壮性。
总结
context包在做并发控制上具有相当方便的功能,如在做任务的取消、超时以及传递隐式参数的情境。
9731

被折叠的 条评论
为什么被折叠?



