[go中channel通道 | 青训营笔记 ]

98 阅读3分钟

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号下标。

image.png

  • 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缓冲区的数据,结束。