看到一個golang
寫的求質(zhì)數(shù)的程序益兄,第一眼看上去很難理解锻梳,理解了之后又覺得很有趣,特此分析一下净捅。
代碼
package main
import "fmt"
// Send the sequence 2, 3, 4, ... to channel 'ch'.
func generate(ch chan int) {
for i := 2; ; i++ {
ch <- i // Send 'i' to channel 'ch'.
}
}
// Copy the values from channel 'in' to channel 'out',
// removing those divisible by 'prime'.
func filter(in, out chan int, prime int) {
for {
i := <-in // Receive value of new variable 'i' from 'in'.
if i%prime != 0 {
out <- i // Send 'i' to channel 'out'.
}
}
}
// The prime sieve: Daisy-chain filter processes together.
func main() {
ch := make(chan int) // Create a new channel.
go generate(ch) // Start generate() as a goroutine.
for i:=0; i<20; i++{
prime := <-ch
fmt.Print(prime, " ")
ch1 := make(chan int)
go filter(ch, ch1, prime)
ch = ch1
}
}
分析
首先疑枯,求質(zhì)數(shù)(素數(shù))有很多種方法,這個代碼用的方法是:
假設(shè)有一個數(shù)n
,已知小于n
的所有質(zhì)數(shù)的集合是arr
,如果arr
中的每個質(zhì)數(shù)都不能夠整除n
蛔六,則n
就是質(zhì)數(shù)荆永。這是因為任意一個合數(shù)都可以分解成質(zhì)數(shù)的乘積废亭。
例如,
因為 2 是質(zhì)數(shù)具钥,初始化 arr=[2]豆村;
當n=3,3 % 2 != 0, 則 n=3是質(zhì)數(shù), arr=[2,3]骂删;
當n=4掌动,4 % 2 == 0, 不滿足要求,則4不是質(zhì)數(shù)宁玫, arr不變坏匪;
當n=5,,5 % 2 != 0, 5 % 3 != 0, 則 n=5是質(zhì)數(shù)撬统, arr=[2,3,5];
... 依此類推
兩個主要的函數(shù)
generate
函數(shù)就是往通道ch
中放入n
(從2
開始)進行篩選适滓。這個ch
是沒有緩沖區(qū)的通道。程序一開始恋追,就啟動一個協(xié)程執(zhí)行generate
函數(shù)(稱之為generate
協(xié)程)凭迹。-
filter
函數(shù)有三個參數(shù),兩個無緩沖區(qū)的通道苦囱,in
和out
嗅绸,和一個質(zhì)數(shù)prime
。首先從in
中取出一個數(shù)據(jù)i
撕彤,如果i
不能夠被prime
整除鱼鸠,則說明它有可能是個質(zhì)數(shù),則將其放入out
通道中羹铅。重點有兩個地方蚀狰,首先
filter
函數(shù)居然有個for循環(huán)(說明不止使用一次),其次职员,out
的數(shù)據(jù)由誰嚷樘!?
主邏輯
for i:=0; i<20; i++{
prime := <-ch // [1]
fmt.Print(prime, " ")
ch1 := make(chan int) // [2]
go filter(ch, ch1, prime) // [3]
ch = ch1 // [4]
}
首先焊切,代碼[1]
從通道ch
中取出一個數(shù)2
扮授,它必然是質(zhì)數(shù)。代碼[2]
新建了一個無緩沖的通道ch1
专肪,代碼[3]
啟動一個協(xié)程運行fliter
函數(shù)(稱之為fliter1
協(xié)程)刹勃,其中參數(shù)in
是ch
,out
是ch1
嚎尤,prime
是2
荔仁,語句[4]
是將通道ch
賦值為了通道ch1
,然后又轉(zhuǎn)到了語句[1]
,這實質(zhì)上就是從ch1
中取數(shù)據(jù)咕晋,也其實就是從fliter1
協(xié)程的out
通道取數(shù)據(jù)雹拄。
fliter1
協(xié)程的in
通道會接收到數(shù)字3
(由generate
協(xié)程放入),經(jīng)過判斷掌呜,將3
放入了out
通道滓玖。在語句[1]
中,從ch
也就是out
通道中取出3
并打印质蕉。
當判斷數(shù)字3
時势篡,如圖所示:
打印出數(shù)字3
之后,又啟動了一個協(xié)程運行fliter
函數(shù)模暗,稱之為fliter2
協(xié)程禁悠,它的參數(shù)in
是通道ch
,實際上就是之前的ch1
兑宇,也就是fliter1
協(xié)程的out
通道碍侦!而out
通道最后又賦值給了ch
。而這時的prime
變成了3
隶糕。這樣做顯而易見瓷产,因為再次新來一個數(shù)的時候,不僅需要看能不能被2
整除(由fliter1
協(xié)程判斷)枚驻,還要看能不能被3
整除(由fliter2
協(xié)程判斷)濒旦。
當判斷數(shù)字4
時,由于fliter1
協(xié)程判斷不通過再登,所有并沒有放入fliter2
協(xié)程尔邓,語句[1]
也就不能取出數(shù),就會阻塞锉矢。
當判斷數(shù)字5
時梯嗽,傳入fliter1
協(xié)程,通過沈撞,再次傳入fliter2
協(xié)程慷荔,繼續(xù)判斷通過,從out
通道傳出缠俺,實際上就是語句[1]
的ch
通道,所有打印出5
贷岸。
當判斷數(shù)字5
時壹士,如圖所示:
所以,可以推理出偿警,數(shù)字n
前面有m
個質(zhì)數(shù)躏救,就會產(chǎn)生m
個fliter
協(xié)程,這些協(xié)程串聯(lián)在一起,每個協(xié)程負責判斷能否被某個質(zhì)數(shù)整除盒使,如果不能崩掘,則傳遞個給下一個協(xié)程進行判斷,直至輸出少办。
總結(jié)
-
ch=ch1
的時候苞慢,原先的ch
并沒有消失,還被之前啟動的fliter
協(xié)程保存著英妓。 - 巧妙使用通道的性質(zhì)挽放,將多個協(xié)程進行串聯(lián)。
- 最開始我還以為是一個并行的篩選質(zhì)數(shù)的代碼蔓纠,結(jié)果并不是辑畦,其實是串行執(zhí)行。