golang channel & select
通过消息来共享数据是golang的一种设计哲学,channel则是这种哲理的体现.
channel定义
var varName chan dataType
dataType非常广泛,可以是基本的string,int等,也可以是map,slice,自定义的type类型,甚至可以是channel。类型非常丰富,因而在golang中很容易做到通过消息来共享数据。
初始化
channel通过make来初始化;未初始化的channel为nil(刚接触的会常常碰到因未初始化而导致的死锁问题).
channel通过箭头<-反向来初始化为单向通道,即只读"<- chanName"、只写"<- chanName",省略箭头表示双通道,默认为双通道.
当buffer size为0或者缺失时,这时我们称之为unbuffered;反之则为buffered channel.
阻塞条件
- 未初始化的channel,一直会阻塞
- unbuffered channel: 读和写同时准备好了,channel才会开始通信,只有读或者写,则会一直阻塞.
- buffered channel里面写数据时,直到写满为止才会阻塞,而读则是直到为channel为空时才会阻塞.
不带buffer的channel很容易实现顺序执行、同步等;而带buffer的channel通常用来处理异步事件,只要往里面一扔就撤.
关闭channel
可以用close来关闭channel,但是close后,里面的数据还是会存在,直到被全部消费掉,而已再有些释放资源的地方,close channel后,通过len(channel)来判断剩余资源,并做相应的处理.
对了,这里还有个小细节:
v,ok := <- chan
很多地方都会说通过上面那种方式来判断channel是否关闭.其实这种说法会带个小坑.如果是buffer channel,close()后,如果里面还有数据的话,上面那种方式还是能取到数据,ok也是为true,什么时候会获取不到呢?那就是里面的数据全都消耗完了之后,ok为false.
因而上面的表达式表述为如下更合理些:
v,ok:=<-chan,用来表述为能否从channel获取数据,如果能取到,ok为true,如果取不到ok为false,同时channel已关闭或不存在.
有个比较常用,但新手会觉得奇怪的地方就是:
chan <- struct{}{}
语义为将一个空数据传递给channel.因为struct{}{}占用的内存非常小,而且我们对数据内容也不关心,通常用来做信号量来处理,例如结束某个service等.
select
表达式:
select{
case :
/*...*/
default:
/*...*/
}
通常用在需要处理多个channel的地方.select会一直堵塞,直到某个case收到消息后,但是如果有default case的话,其他case没收到消息的话,会马上走default case,然后整个select语句结束.
for ... select
通常用for ... select语句来循环处理消息,有两个点需要稍微注意下.
1.�break可以用于select语句,因而在break语句里面是不能中断整个for循环的.那想跳出for循环怎么办呢?用标签:
LOOP:
for{
select{
case ....:
break LOOP //@A
}
}
注意上面@A的地方
2.select语句中是阻塞的,也就是说,直到选中的case执行完了之后,才会进入下一个循环.因而需要耗时的事情不要放在里面处理,可以单独开个go routine来处理.