理解context
在我刚接触context包时,我是有一点迷惑的。因为在其他的编程语言中很少有接触到context包类似的用法。比如在js绘制canvas中的context,也只是作为保留上下文操作来用的。在go语言的context包中,同样也可以当成上下文来理解,但是在看待context提供的能力时,要从以下两点来理解:
- context提供了一种管理多个goroutine的机制。
- context最终形成了一种树形结构。
在深入之前,让我们回忆一下多线程/进程模型中,主线程/进程是如何管理子线程/进程的。如果子线程/进程又派生了其他的线程/进程呢?这一定是一个头疼的问题。
在go语言中,协程也面临了同样的问题。因此官方在go1.7版本中引入了context包。那么context提供了什么样的能力来管理协程呢?先看一个withCancel
的简单的例子
func watch(ctx context.Context) {
for {
select {
case <-ctx.Done():
log.Println("退出")
return
default:
log.Println("执行逻辑")
time.Sleep(2 * time.Second)
}
}
}
func withCancel() {
ctx, cancel := context.WithCancel(context.Background())
go watch(ctx)
go watch(ctx)
go watch(ctx)
time.Sleep(6 * time.Second)
fmt.Println("可以了,通知子协程停止")
cancel()
//为了检测子协程是否停止,如果没有输出,就表示停止了
time.Sleep(5 * time.Second)
}
调用withCancel的输出如下:
2019/09/30 00:18:48 执行逻辑
2019/09/30 00:18:48 执行逻辑
2019/09/30 00:18:48 执行逻辑
2019/09/30 00:18:50 执行逻辑
2019/09/30 00:18:50 执行逻辑
2019/09/30 00:18:50 执行逻辑
2019/09/30 00:18:52 执行逻辑
2019/09/30 00:18:52 执行逻辑
2019/09/30 00:18:52 执行逻辑
可以了,通知子协程停止
2019/09/30 00:18:54 退出
2019/09/30 00:18:54 退出
2019/09/30 00:18:54 退出
可以看到,我们通过context.WithCancel
方法生成了一个ctx和一个cancel,然后主动调用cancel就可以通过所有的子协程退出了。在watch
方法的实现中,我们是通过select
机制来实现的,一旦context的Done()
方法有值,就会调用return退出,否则的话就执行default
中我们的业务逻辑。
这样我们就可以随时通知所有的子协程退出了。在上面说到,context最终形成了一种树形结构,是因为在子协程中也可以继续使用新的协程,这样就形成了一个树形的调用了。
我们在1中使用cancel方法,就可以向下传播,在2~10号协程中全部退出。
简单的了解context
包的使用后,可以看一下context.Context
这个接口,为了简洁,我删除了源代码中的注释。
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
Context
接口总共提供了4个方法。
Deadline()
用来获取当前context的取消时间,第二个返回值ok
等于false的时候,表示没有设置。Done()
方法返回了一个chan
,当chan中读取到值的时候,表示父context已经发起了取消的请求,那么当前协程开始做相关的清理工作然后退出。Err()
返回context的取消原因Value()
方法用来通过一个key获取当前Context上与之对应的值。
理解Context的树形结构
go中大量的库都使用了context机制,比如database/sql
库,net/http
库等等,因为这些库都支持了context,使得我们在程序中很容易通过context来管理所有新建的协程,而不用自己实现复杂的机制来管理。一旦我们需要取消,只需要在root context调用cancel方法即可。
一些基本使用
在上面的例子中,我们使用了withCancel来实例化一个可以手动取消的context。context包中同样提供了一些其他的方法。
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
WithDeadline
可以设置截止时间。会到达指定时间时自动取消。当然也可以调用CancelFunc
来手动取消。
WithTimeout
可以设置在一段时间后自动取消,和WithDeadline
类似。
参考
Go语言实战笔记(二十)
Golang Context深入理解
Go Concurrency Patterns: context