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/...
- 若是這篇文章對你有幫助,請點個贊/喜歡,鼓勵我持續分享,感謝。
- 個人文章列表,點此可查看
- 若是喜歡本文,隨意轉載,但請保留此原文連接。