最近在开发过程当中遇到了几个goroutine通信的问题,我觉得这几个问题非常具有代表性,因此拿出来和大家分享一下。
检查channel是否关闭
开发过程当中有遇到这样的一种情况,需要检查channel是否关闭,如果关闭则不进行相应操作,否则会panic等现象。在golang的select语法当中,default分支可以解决上述问题,请看如下例子:
closechan := make(chan int,0)
dchan := make(chan int,0)
select{
case <- closechan:
fmt.Println("channel already closed.")
return
default:
fmt.Println("channel not closed, do your things")
dchan <- 1 //1
}
go func(){
for{
select{
case data := <- dchan: //2
err := doSomeThing(data) //3
if err != nil /* some thing wrong*/ { //4
close(closechan) //5
}
}
}
}
上述的方式可以在处理dchan
的数据的时候处理异常并不再接受来自dchan的数据。
但是我们需要考虑一种情况,如果在doSomeThing(data)
的过程当中,出现异常(4)
,那么不允许往dchan
发送数据,并将closechan
关闭。在(5)
关闭closechan
之后就不会再进入(1)
的default流程了。
可是这还有问题,那就是如果doSomeThing(data)
处理的过程当中,新来了一个数据进入到了dchan
,那么将会在(1)
和(2)
处阻塞,当doSomeThing(data)
处理完之后,还会多处理一次异常情况,也就是说在(5)
处将会close(closechan)
两次,这样会导致panic: close of closed channel
,所以我们需要在(5)
再写一个相应的default
处理逻辑:
go func(){
for{
select{
case data := <- dchan: //2
err := doSomeThing(data) //3
if err != nil /* some thing wrong*/ { //4
select{
case <-closechan:
//do nothing
return
default:
close(closechan) //5
}
}
}
}
}
检查buffered-channel是否已满
当我们在使用bufferd-channel的时候,我们可能需要检查当前的channel是否已经满了,因为我们可能不希望此时goroutine阻塞,所以可以采用如下的方式进行处理:
cc := make(chan int, 1)
cc <- data1
select {
case cc <- data2:
fmt.Println("data already en-channel")
default:
fmt.Println("cc was full")
}
fan-in
在研究并发map的时候,会考虑到一种shard-map的实现方式,在读取map中的值的时候,需要通过多个小map中获取完整的map值,可以利用channel实现fan-in:
func fanIn(chans []chan int, out chan int) {
wg := sync.WaitGroup{}
wg.Add(len(chans))
for _, ch := range chans {
go func(ch chan int) {
for t := range ch {
out <- t
}
wg.Done()
}(ch)
}
wg.Wait()
close(out)
}