您的位置 首页 golang

golang goroutine与channel经典练习题

题目

计算出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秒

结果对比

测试机

普通方式

多协程方式

macbook i7

71秒

15秒

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

文章来源:智云一二三科技

文章标题:golang goroutine与channel经典练习题

文章地址:https://www.zhihuclub.com/86923.shtml

关于作者: 智云科技

热门文章

发表评论

您的电子邮箱地址不会被公开。

网站地图