背景
在 Go 中,goroutine
的创建成本低,调度效率高,同时存在数十万个 goroutine
并不奇怪。虽然单个 goroutine
使用的内存有限,但是不意味着可以毫无限制的创建 goroutine
。
Never start a goroutine without knowing how it will stop
每次启动 goroutine
时,必须知道 goroutine
何时、如何退出。否则,程序就潜藏着内存泄漏问题。在讨论协程退出前,先了解下协程为何阻塞
协程阻塞
协程阻塞无法自由退出,主要因为以下两点:
- 超时控制
- 流程控制
context
前者,很容易理解。一般来说启动 goroutine
处理事务,对于事务的处理完成时间都有一定的预期 举例:
- RPC调用:最大超时时间不会超过用户的等待时间
- 定时任务:执行一次的时间不应该超过启动的间隔
针对何时退出,Go 中 提供了 Context
用于 goroutine
生命周期管理
- Cancellation via context.WithCancel.
- Timeout via context.WithDeadline.
req, err := http.NewRequest("GET", "https://play.golang.org/", nil) if err != nil { log.Fatalf("%v", err) } ctx, cancel := context.WithTimeout(req.Context(), 1*time.Second) defer cancel() req = req.WithContext(ctx) client := http.DefaultClient resp, err := client.Do(req) if err != nil { log.Fatalf("%v", err) } fmt.Printf("%v\n", resp.StatusCode)
channel & select
后者,相对来说比较难理解一些。尤其是其他语言的使用者,对于他们而言,程序中的流程控制一般意味着:
- if/else
- for loop
在 Go 中,类似的理解仅仅对了一小半。因为 channel 和 select 才是流程控制的重点。
channel 提供了强大能力,帮助数据从一个 goroutine 流转到另一个 goroutine。也意味着,channel 对程序的 数据流
和 控制流
同时存在影响。
- closed 的 channel 永远不会阻塞
package main import "fmt" func main() { ch := make(chan bool, 2) ch <- true ch <- true close(ch) for v := range ch { fmt.Println(v) // called twice } }
- nil 的 channel 总是阻塞
package main func main() { var ch chan bool ch <- true // blocks forever }
- buffered/unbuffered channel 介于两者之间,会因为 channel 是否可以读写阻塞
那么究竟如何判断 channel 能否读写呢?答案就是 select。
select{
case channel_send_or_receive:
//Dosomething
case channel_send_or_receive:
//Dosomething
default:
//Dosomething
}
协程退出
说了这么多,协程怎么退出呢?相信通过以上部分很容易得到结论:
- 超时返回
- 根据 channel 可读状态返回
// 方式一:遍历关闭的 channel
for x := range closedCh {
fmt.Printf("Process %d\n", x)
}
// 方式二:Select 可读 channel
for {
select {
case <-stopCh:
fmt.Println("Recv stop signal")
return
case <-t.C:
fmt.Println("Working .")
}
}
完美退出
协程能够退出就够了么?还不够,完美的退出应该包含以下三点:
- 通知协程退出
- 通知确认,协程退出
- 获取协程最终返回的错误
举个例子:errgroup
func (g *Group) Wait() error { g.wg.Wait() if g.cancel != nil { g.cancel() } return g.err }
本文作者 : cyningsun
本文地址 : https://www.cyningsun.com/01-31-2021/go-concurrency-goroutine-exit.html
版权声明 :本博客所有文章除特别声明外,均采用 CC BY-NC-ND 3.0 CN 许可协议。转载请注明出处!