go version go1.11 windows/amd64
本文爲解讀 參考鏈接1 中的 菊花鏈 一節 的示例程序,此程序和 參考鏈接2 中代碼有些類似:前者有範圍,後者是無限循環。清楚了 參考鏈接1 的邏輯,就能理解 參考鏈接2 的代碼。
測試代碼——測試語句使用藍色字:
package main import ( "fmt" ) // 6.菊花鏈 // 數據從一端流入,從另一端流出,看上去好像一個鏈表 // 過濾器 func xrange2() chan int { // 從2開始自增的整數生成器 var ch chan int = make(chan int) fmt.Println("xrange2 - ch @ ", ch) go func() { // 開出一個goroutine for i := 2; ; i++ { // 直到信道索要數據,才把i添加進信道 fmt.Println("xrange2: i = ", i) ch <- i } } () return ch } func filter(in chan int, number int) chan int { // 輸出一個整數隊列,篩出是number倍數的,不是number的倍數的放入輸出隊列 // in: 輸入隊列 out := make(chan int) fmt.Println("\nfilter - in @ ", in) fmt.Println("filter - number: ", number) go func() { for { i := <- in // 從輸入中取一個 fmt.Println("filter - in @ ", in, ", i = ", i) if i % number != 0 { fmt.Println("放入輸出信道 in @ ", in, ", number = ", number) out <- i // 放入輸出信道 } } } () return out } func main() { // 6.菊花鏈 const max = 10 // 找出10以內的所有素數 // 修改爲10,便於通過測試語句理解邏輯 nums := xrange2() // 初始化一個整數生成器 number := <- nums // 從生成器中抓一個整數(2),作爲初始化整數 fmt.Println("0.main - nums @ ", nums) // number作爲篩子,當篩子超過max的時候結束篩選 for number <= max { fmt.Println(number) // 打印素數,篩子是一個素數 // 源代碼, nums = filter(nums, number) // 篩掉number的倍數 fmt.Println("1.main - nums @ ", nums) number = <- nums } }
測試結果:
xrange2 - ch @ 0xc000050060 xrange2: i = 2 xrange2: i = 3 0.main - nums @ 0xc000050060 2 filter - in @ 0xc000050060 filter - number: 2 1.main - nums @ 0xc0000500c0 filter - in @ 0xc000050060 , i = 3 放入輸出信道 in @ 0xc000050060 , number = 2 3 filter - in @ 0xc0000500c0 filter - number: 3 1.main - nums @ 0xc000050120 xrange2: i = 4 xrange2: i = 5 filter - in @ 0xc000050060 , i = 4 filter - in @ 0xc000050060 , i = 5 放入輸出信道 in @ 0xc000050060 , number = 2 filter - in @ 0xc0000500c0 , i = 5 放入輸出信道 in @ 0xc0000500c0 , number = 3 5 filter - in @ 0xc000050120 filter - number: 5 xrange2: i = 6 xrange2: i = 7 filter - in @ 0xc000050060 , i = 6 filter - in @ 0xc000050060 , i = 7 放入輸出信道 in @ 0xc000050060 , number = 2 filter - in @ 0xc0000500c0 , i = 7 放入輸出信道 in @ 0xc0000500c0 , number = 3 filter - in @ 0xc000050120 , i = 7 放入輸出信道 in @ 0xc000050120 , number = 5 1.main - nums @ 0xc000050180 7 filter - in @ 0xc000050180 filter - number: 7 1.main - nums @ 0xc00001c060 xrange2: i = 8 xrange2: i = 9 filter - in @ 0xc000050060 , i = 8 filter - in @ 0xc000050060 , i = 9 放入輸出信道 in @ 0xc000050060 , number = 2 filter - in @ 0xc0000500c0 , i = 9 xrange2: i = 10 xrange2: i = 11 filter - in @ 0xc000050060 , i = 10 filter - in @ 0xc000050060 , i = 11 放入輸出信道 in @ 0xc000050060 , number = 2 filter - in @ 0xc0000500c0 , i = 11 放入輸出信道 in @ 0xc0000500c0 , number = 3 filter - in @ 0xc000050120 , i = 11 放入輸出信道 in @ 0xc000050120 , number = 5 filter - in @ 0xc000050180 , i = 11 放入輸出信道 in @ 0xc000050180 , number = 7
解讀
參考鏈接1 中存在一個Daisy-chain的示意圖。
說明,第一次看這個程序時,完全沒看懂,這幾天看了更多資料後,再添加了寫調試語句,運行多次才理解了這個程序——下午痛下決心搞明白它,也因此有了本文。
在 xrange2() 函數中,建立了一個 信道XD1,用它來發送 大於等於2 的整數(生成器)。因爲是非緩衝型信道,所以,在其發送後會被阻塞,直到發送的數據被接收,接收後繼續下一個數發送。
在 xrange2() 函數中創建了一個goroutine(協程XC1),用來實現 讓 信道XD1 發送數據,一直在運行,直到主程序(線程)結束。
在 main() 函數中,首先調用 xrange2() 函數建立一個 整數生成器nums(注意它的地址),再把初始的素數2賦值給number——來自信道XD1 發送的第一個數——接收完畢後,信道XD1又繼續發送下一個整數3。
接着進入循環——有限,關鍵來了!調用 filter()函數 並將其返回值賦值給nums——這裏,nums就改變了——測試語句中打印的信道的地址改變了!
說明,孤在理解這裏的時候花了不少時間。
那麼,filter()函數 中做了什麼呢?新建了一個信道out,並把這個信道返回;另外,創建了一個goroutine——包含一個無限循環,從參數 信道in 中 獲取一個值,然後將這個值和傳入的參數(素數)number進行運算比較,如果獲取的數 不能被 number 除盡,那麼,使用信道out發送。無限循環 意味着這個goroutine會一直運行——直到主程序退出。
第一次調用時,從輸入參數信道in中獲取的數是 3,這個信道就是xrange2()函數中建立的信道XD1——這裏收到了3 那麼XD1發送4 然後等待下一次接收。3除以2除不盡,此時,信道out發送3,然後,等待下一次信道XD1發送數據。因爲是無限循環,下一次的數據是4,在filter()函數中建立的第一個goroutine中收到了——信道XD1又發送5(阻塞),但它是2的倍數,因此被忽略。再次循環,受到5,無法被2除盡,使用out發送。
上面已經有兩個goroutine了,現在回到主線程main函數中。
信道nums 成爲了 filter()函數 中的out,獲取了 第一個被2除不盡的 3,3小於max 10,開始 下一輪循環——打印3、再次調用 filter()函數。
filter()函數 新建信道out——地址變了、創建新的一個goroutine!這個新的goroutine裏面的number是 3——篩掉3的倍數,之前的一個是2——篩掉2的倍數。
新的goroutine裏面的的輸入參數信道in爲之前一個goroutine的信道out。在前面,第一個goroutine已經發送到了5,但沒有被接收,因此,阻塞了。
在第二個goroutine的循環中,首先就是接收,接收到前面發送的5——filter()函數 創建的第一個goroutine又繼續運行了 直到發送7。5不能被3除盡,第二個goroutine發送5。
和前面一樣,第二個goroutine的out被賦值給了 主程序main()函數 中的nums——再次改變了nums!在主程序中執行時,nums收到了5——也就是第三個素數。
然後,主程序繼續循環,將filter()函數中新建的信道out當作參數傳遞給下一次filter()函數調用——作爲其參數信道in,繼續運行下去,會出現很多的goroutine。這些goroutine通過信道相連,因此,這種方式就叫做 Daisy-chain(菊花鏈)。
需要注意,每個 goroutine 都會一直運行——直到主程序退出(參考鏈接2 中是不會退出的)。
若是要查找的素數的範圍較大,那麼,會存在成千上萬個goroutine。雖然goroutine消耗的資源(測試將max設置爲10億,CPU利用率一直是100%) 極少,但是,這種方式也是存在缺陷的。
起點是 xrange2() 函數中的 信道XD1,它作爲調用 filter()函數 新建的goroutine 的輸入信道,源源不斷地提供整數;
filter() 函數 中的每一個新建信道out 都作爲 下一次調用 filter()函數 的輸入信道;
filter() 函數 的每次調用都有參數number,此參數爲素數,每次基於number建立一個goroutine,刪除是number倍數的整數;
畫個圖看看:
參考鏈接