Golang 中迭代讀取 channel
歡迎再次來到我的 Go 語言私人教學課堂,今天我們的主題是,我曾經很難理解的 ( 還好現在已經理解了 ):在 Go 程中迭代讀取channels
。
在開始之前,讓我們先回憶一下。我們都知道,一個 Go 程的存活週期是建立在 main 程序之上的,舉個例子:
package main import "fmt" func main() { go func() { fmt.Println("hello there") }() }
只有極低的機率你才有可能看到fmt.Println
列印的資訊,因為有很大機率main()
函式會在列印執行前就結束。
我們同樣知道一個規範的方法
去控制 Go 程的執行,那就是使用Waitgroup
:
package main import ( "fmt" "sync" ) func main() { var wg sync.WaitGroup defer wg.Wait() wg.Add(1) go func() { defer wg.Done() fmt.Println("hello there") }() }
好的,現在讓我們進入今天的正題,我們想在 Go 程中通過管道傳送資訊,很簡單吧?
package main import "fmt" func main() { c := make(chan string) go func() { c <- "hello there" }() msg := <- c fmt.Println(msg) }
等下 ( 誒。。。) 這個方法沒有使用WaitGroup
居然就起到效果了??當一個channel
進入阻塞狀態的時候 , 意味著它正在等待發送 / 接受資料。所以利用這一點,channel
可以用來實現goroutine
之間的同步。
現在讓我們試著向chanenl
中傳送一組strings
資料,並使用range
來接受資料, 以便能夠迭代讀取channel
中的資料:
package main import "fmt" func main() { c := make(chan string) go func() { for i := 0; i < 10; i++ { c <- "hello there" } }() for msg := range c { fmt.Println(msg) } }
結果比較因缺思廳:
hello there hello there hello there hello there hello there hello there hello there hello there hello there hello there fatal error: all Goroutines are asleep - deadlock! goroutine 1 [chan receive]: main.main() /tmp/sandbox697910326/main.go:14 +0x120
為了理解程式執行過程中發生了什麼,我們必須知道使用range
迭代讀取channel
時,當channel
中沒有資料時,這一讀取行為是不會停止的除非channel
被關閉時,好的,知道了這個,那就讓我們來試試關閉它吧。
package main import "fmt" func main() { c := make(chan string) go func() { for i := 0; i < 10; i++ { c <- "hello there" } close(c) }() for msg := range c { fmt.Println(msg) } }
加上close
後程序變得好多了。
接著讓我們來嘗試一些更復雜的,我們使用for
迴圈來啟動goroutines
:
package main import "fmt" func main() { c := make(chan string) for i := 0; i < 10; i++ { go func() { c <- "hello there" }() close(c) } for msg := range c { fmt.Println(msg) } }
結果:
panic: close of closed channel goroutine 1 [running]: main.main() /tmp/sandbox536323156/main.go:12 +0xa0
我們提交這樣一個錯誤:首先從接收端關閉了一個channel
,接著我們再次傳送並關閉已經被關閉的channel
,這就導致了panic
的出現。
就是這樣,那麼就真的沒辦法迴圈讀取這些值了嗎?實際上是有的,這需要使用另一個goroutine
,程式碼如下。
package main import ( "fmt" "sync" ) func main() { var wg sync.WaitGroup defer wg.Wait() c := make(chan string) for i := 0; i < 10; i++ { wg.Add(1) go func() { defer wg.Done() c <- "hello there" }() } go func() { for msg := range c { fmt.Println(msg) } }() }
好了,現在可以從很多個goroutines
中接收資料了!在這個例子中,我們等待for / Go
迭代的完成,接收者goroutine
通過管道使得接收同步,當迴圈傳送資料結束時,主程序也就跟著結束了。
我寫這篇文章也是為了做一個自我梳理,讓自己的思路更清晰一些,所以如果你發現任何不正確不到位的講解說明,請不要猶豫,趕快在下面評論吧。