goroutine做为Golang并发的核心,咱们不只要关注它们的建立和管理,固然还要关注如何合理的退出这些协程,不(合理)退出否则可能会形成阻塞、panic、程序行为异常、数据结果不正确等问题。这篇文章介绍,如何合理的退出goroutine,减小软件bug。git
goroutine在退出方面,不像线程和进程,不能经过某种手段强制关闭它们,只能等待goroutine主动退出。但也无需为退出、关闭goroutine而烦恼,下面就介绍3种优雅退出goroutine的方法,只要采用这种最佳实践去设计,基本上就能够确保goroutine退出上不会有问题,尽情享用。github
for-range
是使用频率很高的结构,经常使用它来遍历数据,range
可以感知channel的关闭,当channel被发送数据的协程关闭时,range就会结束,接着退出for循环。golang
它在并发中的使用场景是:当协程只从1个channel读取数据,而后进行处理,处理后协程退出。下面这个示例程序,当in通道被关闭时,协程可自动退出。并发
go func(in <-chan int) { // Using for-range to exit goroutine // range has the ability to detect the close/end of a channel for x := range in { fmt.Printf("Process %d\n", x) } }(inCh)
for-select
也是使用频率很高的结构,select提供了多路复用的能力,因此for-select可让函数具备持续多路处理多个channel的能力。但select没有感知channel的关闭,这引出了2个问题:less
问题2能够这样解决,通道只由发送方关闭,接收方不可关闭,即某个写通道只由使用该select的协程关闭,select中就不存在继续在关闭的通道上写数据的问题。函数
问题1可使用,ok
来检测通道的关闭,使用状况有2种。spa
第一种:若是某个通道关闭后,须要退出协程,直接return便可。示例代码中,该协程须要从in通道读数据,还须要定时打印已经处理的数量,有2件事要作,全部不能使用for-range,须要使用for-select,当in关闭时,ok=false
,咱们直接返回。线程
go func() { // in for-select using ok to exit goroutine for { select { case x, ok := <-in: if !ok { return } fmt.Printf("Process %d\n", x) processedCnt++ case <-t.C: fmt.Printf("Working, processedCnt = %d\n", processedCnt) } } }()
第二种:若是某个通道关闭了,再也不处理该通道,而是继续处理其余case,退出是等待全部的可读通道关闭。咱们须要使用select的一个特征:select不会在nil的通道上进行等待。这种状况,把只读通道设置为nil便可解决。设计
go func() { // in for-select using ok to exit goroutine for { select { case x, ok := <-in1: if !ok { in1 = nil } // Process case y, ok := <-in2: if !ok { in2 = nil } // Process case <-t.C: fmt.Printf("Working, processedCnt = %d\n", processedCnt) } // If both in channel are closed, goroutine exit if in1 == nil && in2 == nil { return } } }()
使用,ok
来退出使用for-select协程,解决是当读入数据的通道关闭时,没数据读时程序的正常结束。想一想下面这2种场景,,ok
还能适用吗?指针
使用一个专门的通道,发送退出的信号,能够解决这类问题。以第2个场景为例,协程入参包含一个中止通道stopCh
,当stopCh
被关闭,case <-stopCh
会执行,直接返回便可。
当我启动了100个worker时,只要main()
执行关闭stopCh,每个worker都会都到信号,进而关闭。若是main()
向stopCh发送100个数据,这种就低效了。
func worker(stopCh <-chan struct{}) { go func() { defer fmt.Println("worker exit") // Using stop channel explicit exit for { select { case <-stopCh: fmt.Println("Recv stop signal") return case <-t.C: fmt.Println("Working .") } } }() return }
for-range
,由于range
能够关闭通道的关闭自动退出协程。,ok
能够处理多个读通道关闭,须要关闭当前使用for-select
的协程。stopCh
能够处理主动通知协程退出的场景。本文全部代码都在仓库,可查看完整示例代码:https://github.com/Shitaibin/...
- 若是这篇文章对你有帮助,请点个赞/喜欢,鼓励我持续分享,感谢。
- 个人文章列表,点此可查看
- 若是喜欢本文,随意转载,但请保留此原文连接。