Golang并发编程学习笔记(二)

        本文主要讲述一些协程(Goroutine)和管道(Channel)的综合案例,如果对于相关概念不明晰请先看该系列Golang并发编程学习笔记(一)进行了解。本系列后面还会对生产者和消费者模式、协程管道定时任务的应用、定时器的终止与重置等具体案例进行具体的讲解。

目录

协程(Goroutine)和管道(Channel)的综合案例

应用实例1:

具体要求    

思路分析 

代码实现 

应用实例2:协程引入的需求实现 

具体要求

主要思路 

 代码实现

管道的注意事项 

只读和只写管道的意义: 

 管道注意事项defer处理panic报错


协程(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)
		}
	}
}

管道的注意事项 

  1. 声明之后需要make(开辟内存空间)才可以使用
  2. 如果写满了 继续会写报错
  3. 可以声明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后面的代码可以继续运行,但发现异常后该异常方法后面的代码不继续运行

posted @ 2023-02-16 17:56  怜雨慕  阅读(32)  评论(0编辑  收藏  举报