hchan的数据结构

type hchan struct{
    qcount int //当前队列中的元素
    dataqsize int //环形队列长度
    buf unsafe.Pointer //环形队列指针
    elemsize uint16 //每个元素的大小
    closed uint32 //标识关闭状态
    elemtype *_type //元素类型
    sendx uint //队列下标,指示元素写入时存放到队列中的位置
    recvx uint //队列下标,指示元素从队列的该位置读出
    recvq waitq //等待读消息的goroutine队列
    sendq waitq //等待写消息的goroutine队列
    lock mutex //互斥锁,chan不允许并发读写
}

G的阻塞与唤醒:

chan写入数据的goroutine会唤醒读阻塞的goroutine

chan读取数据的goroutine会唤醒写阻塞的goroutine

向chan中写入数据的过程:

  1. 如果等待recvq接收队列不为空,且缓冲区为空或者没有,则取出recvq中的G,写入数据,唤醒G,结束发送。
  2. 如果缓冲区有空余位置,则将数据写入缓冲区,结束发送过程。
  3. 如果缓冲区没有空余位置,则将数据写入G中,将当前G放入sendq中,进入睡眠,等待被读G唤醒。

向chan中读取数据的过程:

  1. 如果等待sendq发送队列不为空,且没有缓冲区,则取出sendq中的G,读取数据,唤醒G,结束接收过程。
  2. 如果等待sendq发送队列不为空,且缓冲区已满,从缓冲区首部读取数据,从sendq中取出G,把G的数据写入缓冲区尾部,把G唤醒,结束读取过程。
  3. 如果缓冲区有数据,则从缓冲区中读出数据,结束读取过程。
  4. 将当前的G加入到recvq中,进入睡眠,等待被写G唤醒。

关闭channel

  1. recq中的G全部唤醒,本该写入G的数据位置为nil,把sendq中的G全部唤醒,但这些G会panic掉。

总结

其实把channel理解成一条管道,左进右出,当channel关闭时,左边进入的G全部panic,右边出去的G全部nil。往channel里读写数据时,先看缓存区,再看等待区,缓存有东西则读写缓存中的,没有则读写等待区中的G。


nlonx9yl
1 声望0 粉丝