题目

计算出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管道内的元素个数等于启动的协程数量时,表示所有协程都完成并退出了。

 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秒

结果对比

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