看到一个golang
写的求质数的程序,第一眼看上去很难理解,理解了之后又觉得很有趣,特此分析一下。
代码
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
}
}
分析
首先,求质数(素数)有很多种方法,这个代码用的方法是:
假设有一个数n
,已知小于n
的所有质数的集合是arr
,如果arr
中的每个质数都不能够整除n
,则n
就是质数。这是因为任意一个合数都可以分解成质数的乘积。
例如,
因为 2 是质数,初始化 arr=[2];
当n=3,3 % 2 != 0, 则 n=3是质数, arr=[2,3];
当n=4,4 % 2 == 0, 不满足要求,则4不是质数, arr不变;
当n=5,,5 % 2 != 0, 5 % 3 != 0, 则 n=5是质数, arr=[2,3,5];
... 依此类推
两个主要的函数
generate
函数就是往通道ch
中放入n
(从2
开始)进行筛选。这个ch
是没有缓冲区的通道。程序一开始,就启动一个协程执行generate
函数(称之为generate
协程)。-
filter
函数有三个参数,两个无缓冲区的通道,in
和out
,和一个质数prime
。首先从in
中取出一个数据i
,如果i
不能够被prime
整除,则说明它有可能是个质数,则将其放入out
通道中。重点有两个地方,首先
filter
函数居然有个for循环(说明不止使用一次),其次,out
的数据由谁取?
主逻辑
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
中取出一个数2
,它必然是质数。代码[2]
新建了一个无缓冲的通道ch1
,代码[3]
启动一个协程运行fliter
函数(称之为fliter1
协程),其中参数in
是ch
,out
是ch1
,prime
是2
,语句[4]
是将通道ch
赋值为了通道ch1
,然后又转到了语句[1]
,这实质上就是从ch1
中取数据,也其实就是从fliter1
协程的out
通道取数据。
fliter1
协程的in
通道会接收到数字3
(由generate
协程放入),经过判断,将3
放入了out
通道。在语句[1]
中,从ch
也就是out
通道中取出3
并打印。
当判断数字3
时,如图所示:
打印出数字3
之后,又启动了一个协程运行fliter
函数,称之为fliter2
协程,它的参数in
是通道ch
,实际上就是之前的ch1
,也就是fliter1
协程的out
通道!而out
通道最后又赋值给了ch
。而这时的prime
变成了3
。这样做显而易见,因为再次新来一个数的时候,不仅需要看能不能被2
整除(由fliter1
协程判断),还要看能不能被3
整除(由fliter2
协程判断)。
当判断数字4
时,由于fliter1
协程判断不通过,所有并没有放入fliter2
协程,语句[1]
也就不能取出数,就会阻塞。
当判断数字5
时,传入fliter1
协程,通过,再次传入fliter2
协程,继续判断通过,从out
通道传出,实际上就是语句[1]
的ch
通道,所有打印出5
。
当判断数字5
时,如图所示:
所以,可以推理出,数字n
前面有m
个质数,就会产生m
个fliter
协程,这些协程串联在一起,每个协程负责判断能否被某个质数整除,如果不能,则传递个给下一个协程进行判断,直至输出。
总结
-
ch=ch1
的时候,原先的ch
并没有消失,还被之前启动的fliter
协程保存着。 - 巧妙使用通道的性质,将多个协程进行串联。
- 最开始我还以为是一个并行的筛选质数的代码,结果并不是,其实是串行执行。