Go基礎系列:雙層channel用法示例
阿新 • • 發佈:2018-11-21
雙層通道的解釋見Go的雙層通道
以下是一個雙層通道的使用示例。注意下面的示例中使用了"訊號通道"(Signal channel),但這裡的訊號通道是多餘的,僅僅只是為了介紹。
訊號通道不用來傳遞資料,而是用來傳遞訊息,用來產生可讀、可寫的事件,以便讓select選中某個分支。
package main import ( "fmt" "time" ) func main() { // 定義雙層通道cc cc := make(chan chan int) times := 5 for i := 1; i < times+1; i++ { // 定義訊號通道f f := make(chan bool) // 每次迴圈都在雙層通道cc中生成內層通道c // 並通過訊號通道f來終止f1() go f1(cc, f) // 從雙層通道cc中取出內層通道ch // 並向ch通道傳送資料 ch := <-cc ch <- i // 從ch中取出資料 for sum := range ch { fmt.Printf("Sum(%d)=%d\n", i, sum) } // 每個迴圈睡眠一秒鐘 time.Sleep(time.Second) // 每次迴圈都關閉訊號通道f close(f) } } // 雙層通道cc用來生成內層通道c // 並使用訊號通道f來終止函式f1() func f1(cc chan chan int, f chan bool) { c := make(chan int) cc <- c defer close(c) sum := 0 select { // 從內層通道中取出資料,計算和,然後發回內層通道 case x := <-c: for i := 0; i <= x; i++ { sum = sum + i } // goroutine將阻塞在此,直到資料被讀走 c <- sum // 訊號通道f可讀時,結束f1()的執行 // 但因為select沒有在for中,該case分支用不上 case <-f: return } }
上面的示例中,函式f1()兩個引數,一個是雙層通道cc,一個是訊號通道f。f1()中首先生成了一個通道c,併發送給了雙層通道cc,使得main()中可以從cc中取得這個內層通道c,並向其傳送資料。
回到f1()中,select最初會被阻塞,因為內層通道c和訊號通道f都沒有資料可讀。由於main()可以取得內層通道c,並向其傳送資料,使得f1()中的select第一個case分支被選中,該分支會計算髮送的整數之前的總和,並將計算結果重新發送給內層通道c,讓main()可以取得這個計算結果。
上面的示例中有幾個細節需要注意:
- 在f1()中必須關閉內層通道c,因為main()中的range迭代一個未關閉的通道會一直阻塞,而且每次呼叫f1()都會重新建立c通道。
- 上面的訊號通道其實沒有起到任何作用。
- f1()中的select必須不能放進for迴圈。因為f1()將資料發回c之後,如果在for中,發f()所在的goroutine將阻塞在select上,由於c通道還沒有關閉,這會導致main goroutine因range迭代操作而阻塞,也就是說所有goroutine都被阻塞了,出現了死鎖。
所以,當在select中有傳送操作的時候,很可能會出現死鎖現象。這時,要麼為select加上default,要麼為select加上超時時間,要麼select不要放在for迴圈中。