一个没有停止的goroutine
当 goroutine 内的任务,运行的太久,又或是卡死了…就会一直阻塞在系统中,变成 goroutine 泄露,或是间接造成资源暴涨,会带来许多的问题。
func main() {
ch := make(chan string, 6)
go func() {
for {
ch <- "脑子进煎鱼了"
}
}()
}
1. 关闭 channel
close 机制来完成对 goroutine 的精确控制
func main() {
ch := make(chan string, 6)
go func() {
for {
v, ok := <-ch
if !ok { //循环到ch close后, ok为false
fmt.Println("结束")
return
}
fmt.Println(v)
}
}()
ch <- "煎鱼还没进锅里..."
ch <- "煎鱼进脑子里了!"
close(ch)
time.Sleep(time.Second)
}
2. for range 的特性
go func() {
for {
for v := range ch { //ch close时 会退出for
fmt.Println(v)
}
}
}()
3. 定期轮询 channel
func main() {
ch := make(chan string, 6)
done := make(chan struct{})
go func() {
for {
select {
case ch <- "脑子进煎鱼了":
case <-done:
close(ch)
return
}
time.Sleep(100 * time.Millisecond)
}
}()
go func() {
time.Sleep(3 * time.Second)
done <- struct{}{}
}()
for i := range ch {
fmt.Println("接收到的值: ", i)
}
fmt.Println("结束")
}
在上述代码中,我们声明了变量 done,其类型为 channel,用于作为信号量处理 goroutine 的关闭。
而 goroutine 的关闭是不知道什么时候发生的,因此在 Go 语言中会利用 for-loop 结合 select 关键字进行监听,再进行完毕相关的业务处理后,再调用 close 方法正式关闭 channel。
若程序逻辑比较简单结构化,也可以不调用 close 方法,因为 goroutine 会自然结束,也就不需要手动关闭了。
4. 使用 context
在 context 中,我们可以借助 ctx.Done 获取一个只读的 channel,类型为结构体。可用于识别当前 channel 是否已经被关闭,其原因可能是到期,也可能是被取消了。
因此 context 对于跨 goroutine 控制有自己的灵活之处,可以调用 context.WithTimeout 来根据时间控制,也可以自己主动地调用 cancel 方法来手动关闭。
func main() {
ch := make(chan struct{})
ctx, cancel := context.WithCancel(context.Background())
go func(ctx context.Context) {
for {
select {
case <-ctx.Done():
ch <- struct{}{}
return
default:
fmt.Println("煎鱼还没到锅里...")
}
time.Sleep(500 * time.Millisecond)
}
}(ctx)
go func() {
time.Sleep(3 * time.Second)
cancel()
}()
<-ch
fmt.Println("结束")
}
“我想在 goroutineA 里去停止 goroutineB,有办法吗?”
答案是不能,因为在 Go 语言中,goroutine 只能自己主动退出,一般通过 channel 来控制,不能被外界的其他 goroutine 关闭或干掉,也没有 goroutine 句柄的显式概念。
参考文章: 脑子进煎鱼了 :https://mp.weixin.qq.com/s/tN8Q1GRmphZyAuaHrkYFEg