计算出50万以内的素数。用普通方法后,再考虑用goroutine加快处理速度。
- 要求
- 使用普通方法进行计算。
- 利用goroutine多协程方式实现。
- 对比两种方法的时间效率差。
说明: 本例是一个使用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秒
结果对比
测试机 |
普通方式 |
多协程方式 |
macbook i7 |
71秒 |
15秒 |
所以看处理大数据计算密集时可以考虑用goroutine方式。但是从编码难度上考虑,goroutine方式要比普通方式要大一些。
,