Go channel系列:
雙層通道的解釋見Go的雙層通道
以下是一個雙層通道的使用示例。注意下面的示例中使用了”訊號通道”(Signal channel),但這裡的訊號通道是多餘的,僅僅只是為了介紹。
訊號通道不用來傳遞資料,而是用來傳遞訊息,用來產生可讀、可寫的事件,以便讓select選中某個分支。產生訊息事件的方式有多種,比如直接關閉通道、傳送false/true布林值等等。
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
", 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迴圈中。