一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第24天,点击查看活动详情。
协程退出
很多时候我们开启一个协程,需要它在某个时间退出,比如说监控一个东西,在某个时刻停止监控。有一种办法是利用全局变量,通过修改全局变量的值来控制协程的状态。但是这种方式需要将全局变量加锁,比较麻烦。有一种升级版方案,通过通道的方式:
func main() {
var wg sync.WaitGroup
wg.Add(1)
stopCh := make(chan bool) //用来停止监控
go func() {
defer wg.Done()
watchDog(stopCh, "监控1")
}()
time.Sleep(5 * time.Second) //先监控5秒
stopCh <- true //发停止指令
wg.Wait()
}
func watchDog(stopCh chan bool, name string) {
for {
select {
case <-stopCh:
fmt.Println(name, "停止指令受到,马上停止")
return
default:
fmt.Println(name, "正在监控....")
}
time.Sleep(1 * time.Second)
}
}
上面代码通过开启for select循环,一直后台监控,五秒后发出停止指令,程序返回退出。
Context
上面的方法可以实现简单的协程退出,但是当需要同时取消多个协程或者定时取消协程的时候就不是那么方便了。这时候就需要用到Context了。
什么是Context
Context是一个接口,它具备手动、定时、超时发出取消信号、传值等功能,主要用于控制多个协程之间的协作,尤其是取消操作。
| 方法 | 解释 |
|---|---|
| Deadline() (deadline time.Time, ok bool) | 获取设置的截至时间 |
| Done() <-chan struct{} | 返回一个只读的channel,类型为struct{} |
| Err() error | 返回取消错误的原因 |
| Value(key interface{}) interface{} | 获取该Context上绑定的值,是一个键值对 |
使用Context
下面使用Context改造上面的代码:
func main() {
var wg sync.WaitGroup
wg.Add(1)
ctx, stop := context.WithCancel(context.Background())
go func() {
defer wg.Done()
watchDog(ctx, "监控1")
}()
time.Sleep(5 * time.Second)
stop()
wg.Wait()
}
func watchDog(ctx context.Context, name string) {
for {
select {
case <-ctx.Done():
fmt.Println(name, "停止指令受到,马上停止")
return
default:
fmt.Println(name, "正在监控....")
}
time.Sleep(1 * time.Second)
}
}
可以看到改动不是很大,只是将原来通道的方式改变了一下,但是却能显著提高能力。
Context树
| 类型 | 解释 |
|---|---|
| 空Context | 不可取消,没有截至时间,主要用于Context树根节点 |
| 可取消Context | 用于发出取消信号,当取消时,子Context也会取消 |
| 可定时取消Context | 定时 |
| 值Context | 存储键值对 |
注意:
- Context不要放在结构体中,要以参数的方式传递
- Context作为函数的参数的时候要放在第一位
- 要使用Context.Background函数生成根节点的Context
- Context传递必须的值
- Context多协程安全