golang---素数筛从单机到并发

使用 golang 的并发特性,可以很好的提高程序的执行速度。其中,并发版本的素数筛是一个经典的例子,通过它我们可以更深刻地理解Go语言的并发特性。

首先,我们看看什么是素数筛:

素数筛

顾名思义,就是将素数筛选出来。首先,我们是怎么判断一个数是素数的呢?我们一般是这样做的:

// 判断整数 n 是否是素数
func isPrime(n int) bool {
	for i := 2; i*i <= n; i++ {
		if n%i == 0 {
			return false
		}
	}
	return true
}

之后,就是暴力循环喽。但是,显然,这样做不够高效。

素数筛算法不是这样。他的思想是这样的:

首先从 2 开始,我们知道 2 是一个素数,那么 2 × 2 = 4, 3 × 2 = 6, 4 × 2 = 8… 都不可能是素数了。

然后我们发现 3 也是素数,那么 3 × 2 = 6, 3 × 3 = 9, 3 × 4 = 12… 也都不可能是素数了。

根据这个算法,我们可以有:

// 返回生成[0,n] 之内的所有素数
func PrimeSieve(n int) []int {
	// 标记数组,用于标记一个数是不是素数,是素数标记为0
	mark := make([]int, n+1) 
	// 0,1 不是素数,从2开始
	mark[0], mark[1] = 1, 1
	for i := 2; i <= n; i++ {
		if mark[i] == 0 {
			for j := 2 * i; j <= n; j += i{
				mark[j] = 1 // 标记为 非素数
			}
		}
	}
	result := make([]int, 0)
	for i := 2; i <= n; i++ {
		if mark[i] == 0 {
			result = append(result, i)
		}
	}
	return result
}

最终,这个算法的时间复杂度是:O(N * loglogN)

并发版素数筛

上面是单机版的素数筛,对于 golang 来说,怎么实现并发版的素数筛呢?

参考《Go语言高级编程》 中的一段,我们有如下程序:

package main

import "fmt"

// 返回生成自然数序列的管道: 2, 3, 4, ...
func GenerateNatural() chan int {
	ch := make(chan int)
	go func() {
		for i := 2; ; i++ {
			ch <- i
		}
	}()
	return ch
}

// 管道过滤器: 删除能被素数整除的数
func PrimeFilter(in <-chan int, prime int) chan int {
	out := make(chan int)
	go func() {
		for {
			if i := <-in; i%prime != 0 {
				out <- i
			}
		}
	}()
	return out
}

func main() {
	ch := GenerateNatural() // 自然数序列: 2, 3, 4, ...
	for i := 0; i < 100; i++ {
		prime := <-ch // 新出现的素数
		fmt.Printf("%v: %v\n", i+1, prime)
		ch = PrimeFilter(ch, prime) // 基于新素数构造的过滤器
	}
}

这段程序,我开始一直不理解,为什么这样可以按序的、准确的输出素数呢?查看函数 PrimeFilter ,觉得channl 里面应该没有数字啊,怎么可以执行过滤呢?还有就是这一段:ch = PrimeFilter(ch, prime)

完全不可理解。

后来,主要到 串联 这两个字。还是对 ``Goroutine `不够了解。

我们看到,在函数GenerateNatural() 中,有一个 for 不断的产生数字。只要他的 channl 中的数字被取出,如:

prime := <-ch // 新出现的素数

那么,这个函数就会源源不断的产生新的数字。而且,这个协程 GenerateNatural 在不出现意外或主函数不退出的情况下,是不会中断的!!!

同理,PrimeFilter() 函数也不会中断。

那么,就意味着,每一次 for 循环时,都会增加一个 Goroutine() !!!

我们可以使用 runtime.NumGoroutine() ,查看这个过程中开启了多少的 Goroutine()

func main() {
	ch := GenerateNatural() // 自然数序列: 2, 3, 4, ...
	for i := 0; i < 5; i++ {
		prime := <-ch // 新出现的素数
		//fmt.Printf("%v: %v\n", i+1, prime)
		fmt.Printf("开启的 Goroutine 数: %d\n",runtime.NumGoroutine())
		ch = PrimeFilter(ch, prime) // 基于新素数构造的过滤器
	}
}
####输出
开启的 Goroutine 数: 2
开启的 Goroutine 数: 3
开启的 Goroutine 数: 4
开启的 Goroutine 数: 5
开启的 Goroutine 数: 6

也就是说,通道从

GenerateNatural() --> PrimeFilter(ch1, prime) --> PrimeFilter(ch2, prime) --> PrimeFilter(ch.., prime)

一层一层的过滤。也就是图:

golang---素数筛从单机到并发_第1张图片

另外,更加优雅的推出,我们可以使用context包

你可能感兴趣的:(Go语言编程,算法,go,并发编程)