解讀使用Daisy-chain(菊花鏈)方式篩選一定範圍內素數的代碼

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倍數的整數;

 

畫個圖看看:

 

參考鏈接

1.Go語言併發的設計模式和應用場景

2.官文An example package

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章