这段时间在研究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下程序运行的不确定性。