深入理解Channel中的Buffer与阻塞
首先举个例子来描述Go语言中不同Goroutine的工作方式。比如一个进程中的两个Goroutine,简单来说就像一个汽车工厂里的两个生产车间,同时在进行汽车部件的生产。每个车间在分到CPU时间片的时候,就可以将自己的生产过程继续下去。这两个车间之间是有墙分隔开的,而Channel就是两个车间之间传递工具或者零件的一个小窗口。两个车间互相不可见,生产过程也互相不干扰,只通过小窗口来互相传递东西。在Goroutine中可以用Channel来传递消息、同步状态。
本章概述中提到过,Channel可以定义为有Buffer的Channel,或者无Buffer的Channel。这其中可是大有玄机,因为Channel会阻塞,而有Buffer和无Buffer的Channel阻塞的条件和行为也大不一样。
无Buffer的Channel
无Buffer的Channel意味着没有缓冲,只有读写双方都准备好的时候,写操作和读操作才能完成,否则就一定会阻塞。实际上是什么意思呢?还是以上面的例子来说明,车间A和车间B之间有一个无Buffer的Channel叫ab,车间A生产过程中需要通过Channel ab往车间B传递一个零件。这时候车间A的工人把一个零件放到小窗ab这里,但是他不能放手,因为ab没有Buffer,只有等对面B车间有一个人来接他手里的零件时,他才能松开手,回去继续车间A的生产过程。这就是无Buffer的Channel阻塞时的状态,写操作时如果该Channel没有被读取,那么写操作将一直被阻塞,直到读一方来读取它。
而对于读操作,同样,如果车间B的工人需要从Channel ab这个小窗取一个零件,取到了才能继续他的生产过程,那么他只能在窗口一直伸手等待,直到另一方来到小窗ab前,把他要取的零件放到他手上,这个取东西的操作才能结束。在此之前,B车间中的生产过程是必须等待的。
有Buffer的Channel
有Buffer的Channel就有了缓冲区,不像无Buffer的Channel,需要读写双方都准备好才能完成读写操作,在有Buffer的Channel上的读写,只要Buffer没满就不会阻塞。写的Goroutine可以放下就走,读的Goroutine可以取了就走。再以工厂车间为例,有Buffer的Channel相当于在两个车间通讯的小窗窗台上放了个盒子,Buffer的大小就是盒子的容量。例如C车间和D车间,相互之间通过Channel cd通讯,假如定义cd时我们使用的Buffer大小是10,那么意味着Channel cd这个小窗里有个大小为10的盒子,C车间的工人到小窗前给D车间传递零件时,无须等D车间的人来取,只要往盒子里放就好了,当然前提是盒子里有空格。如果盒子满了,那么C也必须等到D至少取走一个零件,盒子里空出一个格的时候才能往里放。
简言之,对于带Buffer的Channel的写入方,只有当Buffer满了的时候才会阻塞写入行为。对于读取方,在Buffer为空时会阻塞(和无Buffer时行为相同),直到它能读到值才会往下走。