go 主协程通知子协程退出方法-go面试
方式一:用全局变量
主goroutine使用全局变量通知子goroutine退出
func f() {
defer wg.Done()
for {
fmt.Println("中国")
time.Sleep(time.Millisecond * 500)
if notify {
break
}
}
}
func main() {
wg.Add(1)
go f()
time.Sleep(time.Second * 5)
notify = true
wg.Wait()
}
方式二:用channel+select
通过子goroutine接收channel的值让goroutine退出
var wg sync.WaitGroup
var exitChan = make(chan bool, 1)
func f() {
defer wg.Done()
LOOP:
for {
fmt.Println("中国")
time.Sleep(time.Millisecond * 500)
select {
case <-exitChan:
break LOOP
default:
}
}
}
func main() {
wg.Add(1)
go f()
time.Sleep(time.Second * 5)
exitChan <- true
wg.Wait()
}
但是在某些场景下,例如处理一个请求衍生了很多协程,这些协程之间是相互关联的:需要共享一些全局变量、有共同的 deadline 等,而且可以同时被关闭。再用 channel+select 就会比较麻烦,这时就可以通过 context 来实现。
在这里停止worker1的情况下可以使用channel+select,而停止worker2,3的情况下,channel+select就显得不是非常友好,使用context就可以。
一句话:context 用来解决 goroutine 之间退出通知、元数据传递的功能。
方式三:用context
context方法本质上也是channel,只不过进一步包装,更具有实用性
var wg sync.WaitGroup
func f2(ctx context.Context) {
defer wg.Done()
LOOP:
for {
fmt.Println("上海")
time.Sleep(time.Millisecond * 500)
select {
case <-ctx.Done():
break LOOP
default:
}
}
}
func f(ctx context.Context) {
defer wg.Done()
f2(ctx)
LOOP:
for {
fmt.Println("中国")
time.Sleep(time.Millisecond * 500)
select {
case <-ctx.Done():
break LOOP
default:
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel() //通知子goroutine结束
wg.Add(1)
go f(ctx)
time.Sleep(time.Second * 5)
wg.Wait()
}