开发者学堂课程【Go语言核心编程 - 面向对象、文件、单元测试、反射、TCP编程:协程求素数的代码实现】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/626/detail/9760
协程求素数的代码实现
内容介绍
一、新建文件
二、新建三个管道
一、新建文件
打开 chapter16,新建一个文件夹,命名为 goroutineapply03,这是我们的03案例,接下来再新建一个文件夹,新建一个文件 main.go
组代码
packge main
import (
“fmt”
“time”//需要一个 time 包
)
讲解:开始组函数
Func main(){
}
根据刚才的思路,我们要先建三个管道出来
二、新建三个管道
//根据我们刚才的思路,我们应该新建三个管道出来
intchan := make(chan int ,1000)
讲解:类型是放整数的,大小可以随意,放1000或者100都可以,因为只要有读,就可以
primeChan := make(chan int,2000)//放入结果
//标识退出的管道
讲解:创建第二个,这个就要大一点,但其实弄小一点,做完能够提出来放到一个 main 里面也可以。这里将放大一点的数字。有两个思路后,还有第三个就是退出的一个管道。
exitchan := make(chan bool, 4) // 4个
讲解:这是我们需要一个 bool 形式的,也可以弄其他的,只要适和就行。但这个是明确的,就是4个,因为目前开的协程就是4个。如果要放其他数值,那个这方法就不适用,需要另想其它的办法,一招不能走遍天下,要多动脑筋想想,可以把 bool 换成 main,
//开启一个协程,向 intChan 放入 1-8000个数
讲解:这个我们可以用函数去写,但我习惯单写一个函数
//向 intChan 放入 1-8000个数
func putNum(intChan chan int) {
讲解:需要先把管道传过来,管道的类型是引用
for i := 1; i <= 8000; i++{ //循环
intChan<- i
}
讲解:因为是1-8000,所以就放一个 i,然后退出。要注意,在没有关闭之前,线程就已经开始工作了,并不是关闭才能读,关闭是读到这个位置的时候就会退出,仅有这个作用而已。所以他在往里面放的时候就已经开始工作了,
//关闭 intChan
Close(intChan)
讲解:关闭就意味着结束了,所以说只要做一件事情。
}
//开启4个协程,向 intChan 取出数据,并判断是否为素数(如果是,就放入到 primeChan)
Func primenum(intchan chan int, primechan chan int,exitchan chan bool){
讲解:首先你要清楚这个要传进来是从哪取的,这个是必须的。第二个,你要将 primechan 这个管道传进来。做完后,如果你的 prime 取不到的话,你要往退出的管道里写,所以你还需要传一个东西出来,即 exitchan chan,bool类型,因为它需要三个管道,这种情况下,需要 for 循环
//使用for循环
//Var num int
Var flag bool //
讲解:假定它是一个素数,但是否是我们并不知道,如果在 for 循环中有一个进来了,就说明它不是素数,所以直接将 flag 设为 false,如果不是素数,就直接退出。就会有这样一种情况,除了他自身还有别的数字能整除,就说明他不是素数,就直接退出。在整个判断 num 的过程中,它一直没有进到这个里面来,说明我们的假设是对的,如果是对的呢,就将这个数放入到 primechan 中,因为这个管道是安全的,所以放一个就好。
For{
Num := <-intchan
Flag = true //假设是素数
//判断num是不是素数
for i := 2; I < num;i++ {
if num % 1 == 0 {//说明该 num 不是素数
flag = false
break
}
}
If flag{
//将这个数放入到 primechan
讲解:取个 num,这个 num 可以定到外面去会比较好一点,这里不需要定义,只需要在管道取个东西,取出来后,你就该判断它是不是一个素数了。素数是除了自己其它都不能整除的数字。先在前面定一个 flag。
Primechan<-num
讲解:放入一个 num,放入之后,可以看到它做完一个数又会再去取,会在 intchan 取不到的时候退出,所以这样做我们可能会更好。
Num ,OK= <-intchan
If !ok {//intchan 取不到
Break
}
讲解:发现取不到值后,也应该退出。就可能有两种可能退出来。For循 环的 break 是 for 循环的退出,上面的break 是指退出外循环,就管道取不出来了,两个概念不一样。如果我取到了,那就去判断是不是,如果发现他不是,那就退出再把它加进去,需要不停的这样做。当他最后跳出整个 for 循环的时候,就意味着当前这个协程完成了。
}
Fmt. Println(“有一个 primenum 协程因为取不到数据,退出”)
//这里我们还不能关闭
//向 exitchan 写入 true
exitChan<-true
讲解:这里有一个关键,就是这里能退出来吗?退出的原因是取不出来,但是因为有的人能取出来,而有的人却不能,所以这个时候我们不能把他关闭,你要做这样一件事,向退出的管道 exitchan 写入 true,完成后离开。
Func main(){
intchan := make(chan int ,1000)
primeChan := make(chan int,2000)//放入结果
//标识退出的管道
exitchan := make(chan bool, 4) // 4个
//开启一个协程,向 intChan 放入 1-8000个数
Go putnum(intchan)
讲解:就此时此刻,这个协程就已经开始工作了。接下去来我们要写 putnumber 这个协程。开启四个携程
//开启4个协程,向 intChan 取出数据,并判断是否为素数(如果是,就放入到 primeChan)
讲解:我们要取4个协程放在4个 CPU 上,结果和退出的标识放入,这四个已经是在同时工作了,这个就做完了。
For i :=0; I < 4; i ++{
go putNum(intChan, primeChan, exitChan)
讲解:现在把所有的都协调好了,要不停的写,不停的读,但是如果没有 for 这部分的处理,这些都白干,因为如果不做处理的话,主线程就消失了,所以我们现在要看的就是这个部分。我们现在结果都还没有得出,他现在还处于打开的状态,所以我们要等待他把所有的事都做完,同时还要解决把它关掉的问题,所以这是一个关键点。
//这里我们主线程,进行处理
讲解:我们关键的问题是从 exitChan 管道里取出四个结构,证明大家都完成,判断标准是从退出管道里取出4个 t,说明任务是真的完成,甚至说明 primechan 这个管道里也是全部正确,因为4个协程里都有 true,所以需要 for 循环。
//直接
go func(){
}
For i :=0; i < 4; i ++{
讲解:要从退出管道里取出4个,结果没有重要性,管道没有用,只需要取出4个东西,至于这个地方是什么,不重要;如果取不到4个,那就需要等待。如果结果是4个,那我们也就结束了。
<- exitChan
}
//当我们从 exitChan 取出4个结果,就可以放心的关闭 primeChan
讲解:因为我们真正需要的东西是 primeChan
close(primeChan)
}()
讲解:这一部分跟主线上发生关系也特别不好,所以直接使用 go func()函数让它跑起来,作为一个协程让他去做,让他帮我们看看有没有。
//遍历我们的 primeChan,把结果取出
for {
讲解:取值从primeChan中取,因为有一个协程帮我们关,所以他总有一个时间点取不到,所以我们就直接 break,把结果打出来,
res,ok := <- primeChan
if !ok{
break
}
//将结果输出
Fmt . printf(“素数=%d\n”,res)
}
fmt . println(“main线程退出”)
讲解:在这里提示一句话。最后在资源管理器中检查代码,发现重复代码的就可以不用,例如 //Var num int。因我们选的数字8000太大,可带入较小的数字入80,验证一下,至少应该打出四句话,运行,可以看到下图,这个结果是正确的,有一个素数。但为什么一下就退出来了呢?是因为存的速度低于读的速度而造成的,这个的问题不是很大。
可以让他的速度变得慢一点,添加 time sleep(time millisecond*10),使用毫秒,可以在手册-资料-api-time 中查到,同时需要一个 time 包,看看效果,
可以看到速度变慢,结果也是正确的,但是有一个重点,素数结果的打印是在哪里打印的呢?
for {
res,ok := <- primeChan
if !ok{
break
}
//将结果输出
Fmt . printf(“素数=%d\n”,res)
}
可以看到是在这里打印的,这就很奇怪,打印的时候是在后面打印的,但是统计的时候,Fmt. Println(“有一个primenum 协程因为取不到数据,退出”)这个是在前面的,为什么会这样呢,因为它是同步的,开始执行的时候就是同步的,没有顺序而言,不存在等待的问题,就是一边取,一边就统计。数据量可以调大,调为8000
可以看到输出的是正确的。