go 并发——goroutine&&chan

1 篇文章 0 订阅
1 篇文章 0 订阅

        这段时间在研究go。在看过基本语法后先看了go的并发。看过groutine和chan后写了一个利用无缓存chan生产和消费数据的代码。算是一个helloworld,写之前感觉没有什么问题,写的过程中发现问题多多。

         算法描述如下:定义一个全局的int32数据values作为producer的数据源。producer依次从values中读取数据并放入通道ch中。consumer从通道ch中依次取出数据并打印。

         结果预估:

                 1、因为无缓冲的chan的写入ch<-和chan的读取<-ch都是阻塞的,因此控制台输出应该是producer的打印和consumer的打印依次顺序打印

                  2、go的语法现象,如果通道只是创建而没有make的话会报异常:fatal error: all goroutines are asleep -deadlock!

         代码如下:

package main

import (
	"fmt"
	"sync"
	"runtime"
)

var (
	values=[10]int{0,1,2,3,4,5,6,7,8,9}
	wg = sync.WaitGroup{}//1
	)

func main() {

	runtime.GOMAXPROCS(1)
	//ch := make(chan int)//2
	var ch chan int//3
	wg.Add(2)//4
	go producer(ch)
	go consumer(ch)
	wg.Wait()//5
	fmt.Println("main is finished")

}

func producer(ch chan int){
	defer wg.Done()
	fmt.Println("producer in...")
	for i , v := range values{
		fmt.Printf("生产[%d]=%d%s",i+1,v,"\r\n")
		ch <- v
		runtime.Gosched()//6
	}
	close(ch)//7
}

func consumer(ch chan int){
	defer wg.Done()
	fmt.Println("consuemr in...")
	//for x := range ch{
	//	fmt.Println("消费",x)
	//}
	for ; ;  {
		x,ok := <- ch
		if(ok){
			fmt.Println("消费",x)
		}else{
			fmt.Println("消费结束")
			break
		}
	}
}

        问题分析:

                 1、如代码所示,代码运行后一直报fatal error: all goroutines are asleep -deadlock!。在查看chan的初始化ch := make(chan,int)后一直找不到原因。后来发现,在调用producer和consumer的时候没有用go修饰。而golang是用go开启goroutine的。因此这个是语法不熟悉引起的。

                 2、在解决问题1后,调用producer和consumer都用go修饰,不过还是报fatal error: all goroutines are asleep -deadlock!异常。不过这次虽然报异常但是程序还是会运行,而且结果还是正确的。令我百思不得其解,最后在反复梳理代码逻辑后发现producer结束后没有关闭ch,造成consumer无法终止,go判断出出现思索,因此报了这个异常。

                       3、在问题1和问题2解决后,程序能正常运行了,不过这时候的输出结果是这样的:

consuemr in...
producer in...
生产[1]=0
生产[2]=1
消费 0
消费 1
生产[3]=2
生产[4]=3
消费 2
消费 3
生产[5]=4
生产[6]=5
消费 4
消费 5
生产[7]=6
生产[8]=7
消费 6
消费 7
生产[9]=8
生产[10]=9
消费 8
消费 9
消费结束
main is finished

                           如上所示,问题就出现了,我预期的结果是生产[1]=0 消费0 生产[2]=1 消费1。。。而输出却是会生产两次然后消费两次。开始怀疑难道无缓冲区的chan的读取和写入是不阻塞的?不过思前想后应该不会,那要是这样的话,问题应该出在producer和consumer的执行切换过程中。

      

for i , v := range values{
		fmt.Printf("生产[%d]=%d%s",i+1,v,"\r\n")
		ch <- v
		runtime.Gosched()//6
	}

                         producer在遍历values时先打印,然后阻塞在ch<-v,此时consuemr获取时间片并取出ch中的值,然后producer立马获取时间片便利values并打印且再次阻塞在ch<-v。此时consumer获取时间片后进行打印,然后取出ch中的值,再次打印。因此就有了图中的输出结果。因此,解决办法是添加了代码//6,producer在赋值ch后不立即进行下一次的遍历,而是让出时间片,这样最后的执行顺序就是我预期的了,如下:

consuemr in...
producer in...
生产[1]=0
消费 0
生产[2]=1
消费 1
生产[3]=2
消费 2
生产[4]=3
消费 3
生产[5]=4
消费 4
生产[6]=5
消费 5
生产[7]=6
消费 6
生产[8]=7
消费 7
生产[9]=8
消费 8
生产[10]=9
消费 9
消费结束
main is finished

        虽然这个例子很简单,但是作为初学者的我还是能够总结出来一些东西。比如goroutine的开启,各种各样的死锁情况以及在过个goroutine下程序运行的不确定性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值