Go基礎系列:雙層channel用法示例

Go channel系列html

雙層通道的解釋見Go的雙層通道併發

如下是一個雙層通道的使用示例。注意下面的示例中使用了"信號通道"(Signal channel),但這裏的信號通道是多餘的,僅僅只是爲了介紹。函數

信號通道不用來傳遞數據,而是用來傳遞消息,用來產生可讀、可寫的事件,以便讓select選中某個分支。產生消息事件的方式有多種,好比直接關閉通道、發送false/true布爾值等等code

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,並向其發送數據。htm

回到f1()中,select最初會被阻塞,由於內層通道c和信號通道f都沒有數據可讀。因爲main()能夠取得內層通道c,並向其發送數據,使得f1()中的select第一個case分支被選中,該分支會計算髮送的整數以前的總和,並將計算結果從新發送給內層通道c,讓main()能夠取得這個計算結果。blog

上面的示例中有幾個細節須要注意:事件

  1. 在f1()中必須關閉內層通道c,由於main()中的range迭代一個未關閉的通道會一直阻塞,並且每次調用f1()都會從新建立c通道。
  2. 上面的信號通道其實沒有起到任何做用。
  3. f1()中的select必須不能放進for循環。由於f1()將數據發回c以後,若是在for中,發f()所在的goroutine將阻塞在select上,因爲c通道尚未關閉,這會致使main goroutine因range迭代操做而阻塞,也就是說全部goroutine都被阻塞了,出現了死鎖。

因此,當在select中有發送操做的時候,極可能會出現死鎖現象。這時,要麼爲select加上default,要麼爲select加上超時時間,要麼select不要放在for循環中get

相關文章
相關標籤/搜索