题目

计算出50万以内的素数。用普通方法后,再考虑用goroutine加快处理速度。

  1. 使用普通方法进行计算。
  2. 利用goroutine多协程方式实现。
  3. 对比两种方法的时间效率差。

说明: 本例是一个使用goroutine与channel比较综合的题目,有一定复杂度,需要先仔细分析,有思路后才上代码。

代码实现

即轮询1-50万的正整数,依次计算出素数

package main import ( "fmt" "time" ) // 判断一个数是否是素数 func isPrieme(num int) bool { for i:=2;i<num;i { if num%i == 0 { return false } } return true } func main() { //ch := make(chan int) //创建一个无缓存channel start := time.Now().Unix() for i:=1;i<500000;i { if isPrieme(i) { //fmt.Println(i) } } totolTime := time.Now().Unix() - start fmt.Println("用时(s):",totolTime) }

(base) ➜ study go run test.go 用时(s): 71

使用多协程方式实现

需要借用三个管道来完成题目

1. 用一个协程,将50万个数放入一个管道inChannel中,放入完成后关闭管道。

2. 启动多个协程,同时从inChannel中取数据,并判断是否为素数,如果是素数则将结果放入另一个管道 resultChannel

3. 当某个协程完成操作后,放入一个标记位True,到一个叫flagChannel的管道。这个channel用于标识某个协程已经完成退出,当flagChannel管道内的元素个数等于启动的协程数量时,表示所有协程都完成并退出了。

golang面试知识点(golanggoroutine与channel经典练习题)(1)

package main import ( "fmt" "runtime" "time" ) /* 1. 用一个协程,将1-100个数放入一个channel inChannel中。 2. 启动10个协程,同时从inChannel中取数据,并计算n的平方,将结果值放入一个新的channel resultChannel 3. 10个协程,写完后,放入一个标记位True,到标记channel flagChannel,这个channel用于标识已经完成数据处理 4. 当标记位个数为10时,表示10个协程都不能取数据了(inChannel已经空了),这时才可以关闭管道resultChannel */ // 判断一个数是否是素数 func isPrieme(num int) bool { for i:=2;i<num;i { if num%i == 0 { return false } } return true } func writeData(inChannel chan int){ // 写入100个数据到inChannel for i:=1;i<=500000;i { inChannel <- i //fmt.Println("写入数据data= ",i) } // 写入完成后关闭inChannel close(inChannel) } func readAndDealData(inChannel chan int,resultChannel chan int, flagChannel chan bool){ fmt.Println("启动一个新的协程------------") for { // 从inChannel中拿数据 data,ok := <-inChannel if ok { // 如果拿到数据 //fmt.Println("拿到数据data= ",data) // 对拿到的数据进行处理 if isPrieme(data) { // 处理完毕后,将结果放入结果管道resultChannel resultChannel<- data //fmt.Println("素数=",data) } }else { // 如果没有从inChannel拿到数据,表示这个协程的工作已经结束 break } } // 拿不到数据则表示这个协程已经处理完成,则放一个标记位到flagChannel管道 flagChannel <- true } func main(){ fmt.Println("hello world") // 设置可用处理器个数 cpuNum := runtime.NumCPU() runtime.GOMAXPROCS(cpuNum-1) fmt.Println("cpuNum=",cpuNum) // 启动协程的数量 gorutNum := 8 //1 --- 69,2 --- 35s,4 --- 21 // 保存输入数据 inChannel := make(chan int, 10000) // 保存计算结果 resultChannel := make(chan int, 100000) // 保存退出标记位,如果flagChannel一旦有数据,标识已经处理完成,主进程检查到后就退出 flagChannel := make(chan bool, gorutNum) start := time.Now().Unix() // 启动写数据的协程 go writeData(inChannel) // 启动10个同时拿数据并处理数据的协程 for i:=0;i<gorutNum;i { go readAndDealData(inChannel,resultChannel,flagChannel) } // 阻塞主进程,等待所有协程完成 for i:=0;i<gorutNum;i { _,ok := <-flagChannel if ok { // 一旦有数据,表示所有协程已经处理完成 fmt.Println("一个协程处理完毕!") } } close(flagChannel) fmt.Println("Done!") totolTime := time.Now().Unix() - start fmt.Println("总共用时(s): ", totolTime ) }

(base) ➜ 04 go run main.go hello world cpuNum= 8 启动一个新的协程------------ 启动一个新的协程------------ 启动一个新的协程------------ 启动一个新的协程------------ 启动一个新的协程------------ 启动一个新的协程------------ 启动一个新的协程------------ 启动一个新的协程------------ 一个协程处理完毕! 一个协程处理完毕! 一个协程处理完毕! 一个协程处理完毕! 一个协程处理完毕! 一个协程处理完毕! 一个协程处理完毕! 一个协程处理完毕! Done! 总共用时(s): 15

可以看出,使用多协程方式来计算50万以内的素数只用了15秒

结果对比

测试机

普通方式

多协程方式

macbook i7

71秒

15秒

所以看处理大数据计算密集时可以考虑用goroutine方式。但是从编码难度上考虑,goroutine方式要比普通方式要大一些。

,