引
上回聊到,context 是有父子间影响的,父 context cancel 之后,子 context 也会随之 cancel。 这次聊下广播通知多个业务 go 程退出
如何通知多个业务 go 程退出
问题描述
如果在一个业务中会起多个 go 程做一些事,每个 go 程里面会跑流式业务(流式业务可以理解为 dong dong dong 有很多步骤)。你会如何做?
-
基于 bool flag 吗?那你要把所有的 select chan 的地方修改成轮训,改动太大,select 多路复用的优点全没了,引入 cpu 空转。关键有时候还不能这么修改
-
每次退出的时候发个事件到一个专门的 message chan? 基于计算的方式,万一少发了一个,就会造成 go 程泄漏。而且 message chan 的大小也是一个头疼的地方。
-
sync.WaitGroup? WaitGroup 只是 go 程同步等待的函数,不是事件通知用的
基于事件通知的做法
在聊之前,大家估计都知道了两个事情,如果一个 chan 被 close 掉,是可以读取多次的,close 掉一个 close 掉的 chan 是会 panic 的。ok,基于这两点认知,我们设计通知多个业务 go 程的退出代码。
```go
func testQuit(failId int, max int) {
lock := sync.Mutex{}
wg := sync.WaitGroup{}
closeOk := false
done := make(chan struct{})
wg.Add(max)
defer wg.Wait()
for i := 0; i < max; i++ {
go func(id int) {
defer wg.Done()
if id == failId || id == failId-1 {
lock.Lock()
if !closeOk {
close(done)
closeOk = true
}
lock.Unlock()
}
select {
case <-done:
fmt.Printf("id = %d, quit\n", id)
}
}(i)
}
}
我们利用一个 chan 被 close 是可以读取多次的特性,用作广播通知。为了避免一个 chan 会 close 两次会 panic 加入一个 flag 变量。为了 go 程安全,引入 sync.Mutex。
用 context 改造广播通知代码
context 里面实现的代码也是用 close chan 的方式实现的,可能官方看大家都是用这种方式用作广播通知,最后把这种模式加入到了标准库里面
package main
import (
"context"
"fmt"
"sync"
)
func testContext(failId int, max int) {
var wg sync.WaitGroup
ctx, cancel := context.WithCancel(context.Background())
wg.Add(max)
defer wg.Wait()
for i := 0; i < max; i++ {
go func(id int) {
defer wg.Done()
if id == failId {
cancel()
}
select {
case <-ctx.Done():
fmt.Printf("id = %d, quit\n", id)
}
}(i)
}
}
func main() {
testContext(1, 5)
testContext(0, 5)
}