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\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循環中。