1.什么是channel
channel是一种进程之间的通信方式,可以在进程,线程,协程之间互相通信
1.1 如何创建channel
makechan(type,size) 使用这个函数进行channel的创建,type表示创建接收什么type数据类型的缓冲区,size表示创建多大的缓冲区。
2.channel的数据结构
类似于其他语言中的管道通信,channel在创建后也可以进行读和写两种方式,不过channel会初始化通道的大小,发送队列,接受队列,还有维护环形缓冲区的几个指针
2.1 concrete struct
下面是channel在src/runtime/chan.go:hchan中定义的数据结构
type chan struct{
qcount uint //当前缓冲区中剩余元素个数
dataqsize uint //环形缓冲区长度
buf unsafe.Pointer //环形缓冲区的头指针
elemsize uint16 //缓冲区保存元素的大小
closed unit32 //保存管道是否关闭的状态
elemtype *_type //元素的类型
sendx uint //输入下标,保存元素进缓冲区保存的坐标
recvx uint //输出下标,保存元素从缓冲区读出的坐标
sendq waitq //等待写数据的goroutine队列
recvq waitq //等待读数据的goroutine队列
lock mutex //互斥锁,channle不允许并发读写
}
2.2 环形队列(缓冲区)
channel中保存着一个缓冲区,这个缓冲区用来接收数据,并且暂时存放,以供读取。 下图展示了一个缓冲区大小为8,保存3个元素,元素问别再在7,0,1号下标。
- dataqsize保存着缓冲区的大小,大小可以为0
- qcount当前缓冲区保存元素的总个数
- buf是保存缓冲区的指针
- sendx保存下一个元素输入的坐标
- recvx保存下一个输出元素坐标
2.3 等待队列
channle保存两个等待队列,队列保存的是goroutine,分别是读队列和写队列
sendq 写队列
recvq 读队列
向channel中读数据,如果缓冲区为空,此时会对当前使用channel的goroutine阻塞,并且加入到读队列中。向channel中写数据,如果缓冲区为满,此时会对当前使用channel的goroutine阻塞,并且加入到写队列中。
如何唤醒?
- 如果有其他的goroutine往channel中写数据,此时读队列中的goroutine就会被唤醒
- 如果有其他的goroutine往channel中读数据,此时写队列中的goroutine就会被唤醒
3.channel的读写
3.1 写channel的数据
- 从channel中进行写数据,如果recvq队列不为空的时候,说明我recvq队列有等待的取数据的程序,也就意味着我此时的缓冲区没有数据,或者缓冲区大小dataqsize为0。
- 此时应该取recvq中的goroutine直接进行数据读取,因为没有缓冲区去暂存数据,直接进行读取,然后唤醒这个recvq的goroutine,结束。
- 如果recvq的队列为空,意味着没有等待读数据的goroutine,判断buf缓冲区有没有空间,有则将数据插入到队尾,结束。
- 如果没有空间,则将写数据的goroutine阻塞,插入到sendq队列中,等待被唤醒。
3.2 读channel数据
- 从channel中进行读数据,如果sendq不为空,说明sendq队列中有等待写数据的程序,也就意味这我此时缓冲区满了,或者缓冲区dataqsize大小为0。
- 当时缓冲区datasize大小为0的时候,直接读取sendq队列中goroutine的数据,然后唤醒此goroutine。结束。
- 当时缓冲区满的时候,读取缓冲区中元素,并且写入新的数据,新的数据是从sendq中的goroutine写入,写入的同时唤醒此goroutine,结束。
- 当sendq为空,说明没有正等待写的程序,此时buf缓冲区可能有数据,也可能为空。若为空,没有数据可以读,将此读数据的goroutine放入recvq队列中,等待被唤醒,结束。
- 若有数据,取buf缓冲区的数据,结束。