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


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()可以取得這個計算結果。

上面的示例中有幾個細節需要注意:

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM