Golang并发编程学习笔记(二)
本文主要讲述一些协程(Goroutine)和管道(Channel)的综合案例,如果对于相关概念不明晰请先看该系列Golang并发编程学习笔记(一)进行了解。本系列后面还会对生产者和消费者模式、协程管道定时任务的应用、定时器的终止与重置等具体案例进行具体的讲解。
目录
协程(Goroutine)和管道(Channel)的综合案例
协程(Goroutine)和管道(Channel)的综合案例
应用实例1:
具体要求
用协程(Goroutine)和管道(Channel)协同工作完成
1、开启一个writeDate协程,向管道intChan中写入50个整数
2、开启一个readDate协程,从管道intChan中读取writeDate写入的数据
注意:读写操作的是同一个管道的数据。主线程需要等待读写的协程完成后才能退出。
思路分析
因为需要快,需要两个协程同步进行,但是读的更快、写的更慢,此时我们再给他一个管道。读完50个数据给exit写一个true,并关闭chan通道。等到main里面的for循环在exitChan中取到true了,就结束循环。
代码实现
var intChan chan int
var exitChan chan bool
//封装两个方法writeDate&readDate
func writeDate(intChan chan int) {
rand.Seed(time.Now().UnixNano())
for i := 1 ; i <= 50 ; i++ {
var tempInt int
tempInt = rand.Intn(4) + 10
intChan <- tempInt //写入值
fmt.Printf("第%v次写入,写入%v\n",i,tempInt)
}
close(intChan) //注意关闭通道
}
func readDate (intChan chan int,exitChan chan bool) {
var count int
for {
val,ok := <-intChan
count++
if !ok {
break
}
fmt.Printf("第%v次读取,读到%v\n",count,val)
}
exitChan <- true
close(exitChan) //注意关闭通道
}
func main() {
intChan = make(chan int,50)
exitChan := make(chan bool,1)
go writeDate(intChan)
go readDate(intChan,exitChan)
// time.Sleep(time.Second*2) //无法预测
if <- exitChan {
fmt.Println("---------------end main---------------")
return
}
}
应用实例2:协程引入的需求实现
具体要求
统计1~2000000的数字中哪些是素数
传统方式:使用循环判断
优化:使用并发和并行的方式
Goland:将统计分配给多个goroutine去完成
主要思路
代码实现
Goroutine+channel实现
var intChan chan int = make(chan int, 1000000)
var primeChan chan int = make(chan int, 1000000)
var exitChan chan bool = make(chan bool,8)
func main() {
// isPrimeA(1000000)
// fmt.Println()
go initChan(1000000)
for i := 0 ; i < 8 ; i++ { //开启8个isPrimeB协程
go isPrimeB(intChan,primeChan,exitChan)
}
go func () {
for i := 0 ; i < 8 ; i++ {
<-exitChan
}
close(primeChan)
}()
for {
res,ok := <- primeChan
if !ok {
break
}
fmt.Printf("%v\t",res)
}
}
func initChan(num int) { //初始化
for i := 1; i <= num; i++ {
intChan <- i
}
close(intChan)
}
func isPrimeB(intChan chan int, primeChan chan int, exitChan chan bool) {
var flag bool
for {
num , ok := <- intChan
if !ok {
break
}
flag = true
for j := 2; j < num; j++ {
if num%j == 0 {
flag = false
break
}
}
if flag {
primeChan <- num
// fmt.Printf("数字%v是素数\n", num)
}
}
exitChan <- true
}
//基础方法,时间慢
func isPrimeA(num int) {
for i := 1; i <= num; i++ {
var flag bool = true
for j := 2; j < i; j++ {
if i%j == 0 {
flag = false
break
}
}
if flag {
fmt.Printf("数字%v是素数\n", i)
}
}
}
管道的注意事项
- 声明之后需要make(开辟内存空间)才可以使用
- 如果写满了 继续会写报错
- 可以声明chan只读或者只写
只写 var chanIn chan <- int
只读 var chanOut <- int
只读和只写管道的意义:
方法参数 控制只读只写。防止误操作
底层处理 效率也会更高
func isPrimeB(intChan chan int, primeChan chan int, exitChan chan bool)
比如这个方法的参数一 只读;参数二三只写
Closed的继续写也会报错,但可以读。如果没有close去读取 会死锁
引入关键字 select
label for {select {case := default return}}
label :
for {
select {
case res := <-primeChan:
fmt.Printf("%v\t",res)
default:
break label
}
}
管道注意事项defer处理panic报错
func onlyOut(num int) {
var testMap map[int]int
testMap[0] = num
}
报错:panic: assignment to entry in nil map、(main中go onlyOut后面的代码不会在运行)
更改:
func onlyOut(num int) {
defer func () {
if err := recover();err != nil {
fmt.Println("onlyOut方法异常(panic)",err)
}
}() //发现异常后该方法后面的代码不继续运行
var testMap map[int]int
testMap[0] = num
}
main中go onlyOut后面的代码可以继续运行,但发现异常后该异常方法后面的代码不继续运行