对Channel的使用方法和底层机制进行记录。
channel 的内部实现是在src/runtime/chan.go
文件里的 hchan 结构体。很多别的 builtin 函数,结构的源代码也在这个位置。
总体上来说,channel 是利用了一个环形数组来以队列的形式存储管道里的数据(之后所说的环形数组、队列和缓冲区,所指的就是这个数据结构),并且利用两个队列来存储等待中的待读协程和待写协程。
一个 channel,其内部有如下数据:
gotype hchan struct {
qcount uint // total data in the queue
dataqsiz uint // size of the circular queue
buf unsafe.Pointer // points to an array of dataqsiz elements
elemsize uint16
closed uint32
elemtype *_type // element type
sendx uint // send index
recvx uint // receive index
recvq waitq // list of recv waiters
sendq waitq // list of send waiters
// lock protects all fields in hchan, as well as several
// fields in sudogs blocked on this channel.
//
// Do not change another G's status while holding this lock
// (in particular, do not ready a G), as this can deadlock
// with stack shrinking.
lock mutex
}
创建 channel 时,所调用的make(chan int,10)
语句中,int 对应的即为 elemtype,10对应的即为dataqsiz 。
初始化一个 channel 有两种方法:var c chan int
,该 channel 会被初始化为 nil;c := make(chan int,10)
,它会创建一个对应的 channel 赋值给c,该 channel 是有缓冲区的。如果传入 make 函数的长度为0,那么该 channel 是无缓冲区的。
<-
为channel的操作符。如果 channel 变量在左表示数据进入 channel ,反之 channel 变量在右则表示数据从 channel 中读取。该操作符还可以限制 channel 的读写类型。函数参数中可以有<- channel
类型的参数和channel <-
类型的参数,分别是在函数内只能读和只能写的 channel。
另外,从上文的数据结构中可以看出来,go 的底层并没有对 channel 的读写做限制,所以这里只是一个类型限制。
a := <- c
和 a, ok := <- c
。ok变量用于指示数据是否读取成功,但并不指示 channel 是否关闭。如果 channel 为开启状态,那么读取要么有数据要么阻塞。反之,channel 为关闭状态,如果 channel 内还有数据,那么读取成功。只有在 channel 被关闭且 channel 内无数据,ok才会为false。互斥锁加锁解锁就不说了,每次操作都会有。
channel 在写入时如果缓冲区有空间可以写,那么数据会进入缓冲区。反之,如果没有空间,那么协程会进入等待写入队列并且被休眠。如果等待读取队列不为空,那么数据会直接返回给等待读取的协程,而不进入缓冲区。
相同的,对 channel 进行读取时,如果缓冲区有数据可以读,那就从缓冲区取出数据,否则,读取协程进入等待队列睡眠,等待唤醒。如果待写队列不为空,那么数据会直接进入读取协程,不再进入缓冲区。
关闭 channel 时,待读队列内的所有协程会被唤醒,并且获取到 channel 对应类型的默认值;待写队列内的所有协程也会被唤醒,但是会引发 panic。如果关闭一个已经被关闭的 channel,也会引发 panic。
本文作者:御坂19327号
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!