goroutine-stop

在 Go 的开源项目中, 经常能够看到通过关闭一个 stopc 的通道来退出 goroutine 的写法, 如下面这个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package main

import (
"fmt"
"time"
)

func main() {
stopc := make(chan struct{})

go func() {
for {
select {
case <-stopc:
fmt.Println("a stop")
return
case <-time.After(time.Second):
fmt.Println("a default")
}
}
}()

go func() {
for {
selec t {
case <-stopc:
fmt.Println("b stop")
return
case <-time.After(time.Second):
fmt.Println("b default")
}
}
}()

time.Sleep(2 * time.Second)
close(stopc)
time.Sleep(time.Second)
}

运行上面的程序会得到结果

1
2
3
4
5
6
a default
b default
b default
a default
b stop
a stop

可以看到,在执行两次的打印任务后,goroutine 都完成了退出。

为什么使用 close(stopc) 可以完成对两个 goroutine 的退出控制,而不是往 stopc 中发送两次数据呢?

在 go 语言的官方规格文档对 close 函数的说明中可以找到答案

For an argument ch with a core type that is a channel, the built-in function close records that no more values will be sent on the channel. It is an error if ch is a receive-only channel. Sending to or closing a closed channel causes a run-time panic. Closing the nil channel also causes a run-time panic. After calling close, and after any previously sent values have been received, receive operations will return the zero value for the channel’s type without blocking. The multi-valued receive operation returns a received value along with an indication of whether the channel is closed.

大意是当我们使用 close 关闭一个 channel 时,这标志着没有更多的数据发送到 channel 中了。当使用 close 关闭了一个 channel ,并且 channel 中的所有数据都被消费后,使用 <- 操作符从 channel 中读取数据时,将会返回 channel 数据类型的零值,并且不会被阻塞

根据官方当我们使用 close 关闭了 stopc 后,goroutine 中的 select 作用域中的 stopc 分支将不会阻塞,直接返回零值。即使有多个消费者在监听,都将会返回零值,从而实现退出。

因此当需要控制多个 goroutine 的推出时,可以采用相同的方法,传入一个 stopc 的 channel,在需要退出时 close(stopc) 即可。


goroutine-stop
https://blog.zhangliangliang.cc/post/goroutine-stop.html
作者
Bobby Zhang
发布于
2023年10月19日
许可协议