Go协程与协程池
1. Golang協程
- golang和其它語言最大區別莫過于goroutine,也就是go的協程,example如下:
輸出:
this is go worker : lineshen this is go worker : lineshen this is go worker : lineshen this is go worker : lineshen this is go worker : lineshen this is go worker : lineshen this is go worker : lineshen this is go worker : lineshen this is go worker : lineshen this is go worker : lineshen this is go worker : glorialu this is go worker : glorialu this is go worker : glorialu this is go worker : glorialu this is go worker : glorialu this is go worker : glorialu this is go worker : glorialu this is go worker : glorialu this is go worker : glorialu this is go worker : glorialu main finished如果不用協程,上面代碼應該三個線程運行,分別是主函數main和 兩個函數。 使用協程之后,實際運行兩個線程,這就是并發的好處。
Note:當使用go啟動協程之后,這2個函數就被切換到協程里面執行了,但是這時候主線程結束了,這2個協程還沒來得及執行就會掛了!所以不在main中加time進行sleep就無法看到協程中的執行結果。
- 多次運行,其結果可能如下:
會發現lineshen和 glorialu 的順序不是固定的,這進一步說明了一個問題,那就是多個協程是同時執行的。不過睡眠這種做法肯定是不靠譜的,go 自帶一個WaitGroup可以解決這個問題,。
- WaitGroup示例如下:
sync.WaitGroup也起到了time.Sleep的效果。但是協程內部的執行順序是不確定的。
sync.WaitGroup用法: var 是聲明了一個全局變量 wg,類型是sync.WaitGroup,wg.Add(2) 指2個goroutine需要執行,
wg.Done 相當于 wg.Add(-1) 意思就是該協程執行結束;wg.Wait()通信主線程wait,等2個協程都執行完再退出。
應用實例:從3個庫取不同的數據匯總處理,最簡單寫法就是查3次庫,但是這3次查詢必須按順序執行,大部分編程語言的代碼執行順序都是從上到下,假如一個查詢耗時1s,3個查詢就是3s,但是使用協程你可以讓這3個查詢同時進行,也就是1s就可以搞定。
2. Golang協程池與channel機制
- 同類型channel
如何將協程中結果返回給主線程?golang提供了channel機制。示例如下:
package mainimport ("fmt""sync") var wg sync.WaitGroupfunc go_worker(name string, ch chan string) {for i:=0; i<10; i++ {ch <- name}wg.Done() }func main(){wg.Add(2)ch := make(chan string)go go_worker("lineshen", ch)go go_worker("glorialu", ch)for {fmt.Println("this is go worker :" , <-ch)}wg.Wait()fmt.Println("main finished") }代碼執行結果如下。就是實例化了一個channel,go啟動的協程同時向這個2個管道輸出數據,主線程使用了一個for循環從管道里面取數據。其實就是一個生產者-消費者模式,與redis隊列有點像。下面也可以看到lineshen和glorialu進入管道的順序是不固定的。如果實驗發現輸出順序固定的,那是因為電腦跑的太快了,可以把循環開的大一點。
this is go worker : glorialu this is go worker : glorialu this is go worker : lineshen this is go worker : glorialu this is go worker : lineshen this is go worker : glorialu this is go worker : lineshen this is go worker : glorialu this is go worker : lineshen this is go worker : glorialu this is go worker : lineshen this is go worker : glorialu this is go worker : lineshen this is go worker : glorialu this is go worker : lineshen this is go worker : glorialu this is go worker : lineshen this is go worker : glorialu this is go worker : lineshen this is go worker : lineshen fatal error: all goroutines are asleep - deadlock!也看到發生了致命的錯誤:fatal error: all goroutines are asleep - deadlock! 所有的協程都睡眠了,程序監測到死鎖!go的channel默認是阻塞的(假如不設置緩存),需要“放一個-取一個”。如果channel里面放置的數據沒有取出來,程序就會一直等下去,死鎖了;同時,如果channel里面沒有放置數據,主線程去取數據也會發生死鎖!
如何解決這個問題呢?標準的做法是主動關閉管道,或者知道應該什么時候關閉管道。對于示例代碼,可以簡單加入一個計數器判斷何時channel中的數據可以完全被取出。如下:
package mainimport ("fmt""sync") var wg sync.WaitGroupfunc go_worker(name string, ch chan string) {for i:=0; i<10; i++ {ch <- name}wg.Done() }func main(){wg.Add(2)ch := make(chan string)go go_worker("lineshen", ch)go go_worker("glorialu", ch)counter := 1for {fmt.Println("this is go worker :" , <-ch)if counter >= 20 {close(ch)break}counter++}wg.Wait()fmt.Println("main finished") }輸出如下:
this is go worker : glorialu this is go worker : glorialu this is go worker : lineshen this is go worker : glorialu this is go worker : lineshen this is go worker : glorialu this is go worker : lineshen this is go worker : glorialu this is go worker : lineshen this is go worker : glorialu this is go worker : lineshen this is go worker : glorialu this is go worker : lineshen this is go worker : glorialu this is go worker : lineshen this is go worker : glorialu this is go worker : lineshen this is go worker : glorialu this is go worker : lineshen this is go worker : lineshen main finished- 多種類型channel,使用select進行判斷
輸出結果如下:
Received on channel 1: 0 Received on channel 2: lineshen Received on channel 1: 1 Received on channel 2: lineshen log... Received on channel 2: lineshen Received on channel 1: 2 log... log... Received on channel 1: 3 Received on channel 2: lineshen log... Received on channel 1: 4 Received on channel 2: lineshen log...該程序建立了2個管道一個傳輸int,一個傳輸string,同時啟動了3個協程,前2個協程非常簡單,就是每隔1s向管道輸出數據,第三個協程是不停的從管道取數據,并通過定時器功能可以每隔一段時間向管道輸出內容!
不同的是,go_worker1_int和go_worker2_string是2個不同的管道,通過select可以實現在不同管道之間切換,哪個管道有數據就從哪個管道里面取數據,如果都沒數據就等著。
總結
- 上一篇: HashMap底层实现和原理
- 下一篇: 代码写累了来这看看,笑笑