GO的内置数据结构-channel

1、channel

channel分为有buffer的和没有buffer的。
没有buffer的能够当成有buffer可是buffersize为0的状况。
buffer数据结构:数组

type hchan struct {
    qcount   uint           // 当前chan中有多少数据
    dataqsiz uint           // 环形数组队列的大小,也就是咱们定义的缓冲区大小
    buf      unsafe.Pointer // 指向环形数组队列的指针
    elemsize uint16
    closed   uint32
    elemtype *_type // element type
    sendx    uint   // 发送时插入的位置(环形数组的下标)
    recvx    uint   // 接收时取数据的位置(环形数组的下标)
    recvq    waitq  // 接收链表,当buf为空的时候,打包goroutine现场后放在这里
    sendq    waitq  // 发送链表,当buf满的时候,打包goroutine现场后放在这里
    // 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中,每次qcount和sendx会随之变化,sendx会在插入前标志当前的插入位置变到插入后标志下一个数据插入位置(因为是环形数组,因此若是在最后位置插入后索引归0
当buf里面的数据满的时候,再往里面发送数据,此时qcount==dataqsize表示满,此时咱们会将当前G的现场与channel打包成一个sudog的结构,链在sendq上。数据结构

上图为正常的发送流程,用以演示各个字段在流程中的变化。事实上发送时还须要判断recvq链表是否有sudog:

咱们知道,sendq中存放的是等待发送的sudog,那么一样的recvq存放的就是等待接收的sudog。能够想象到,当recvq中有sudog节点的时候就说明咱们的缓冲区已经没有数据能够取了,才会将接收的g放到recvq中。此时,咱们须要发送的内容应该当即被拿走,不应再放到buf或sendq中。
完整的发送流程以下:
ui

接收流程

一样的,当作从channel中接收数据的动做时,会先判断buf是否为空,为空的话进行现场打包成sudog链在recvq的链表上。
完整的接收流程以下:

与发送流程有所不一样的是,当buf数据满而且sendq中有sudog的时候,咱们还须要判断是否有缓冲区。this

  1. 若是有缓冲区的话咱们须要维护buf:spa

    1. 先将当前的recvx索引的数据取出
    2. 而后将sudog中的elem数据取出
    3. 再将sudog取出的数据copy到buf空出来的位置。(sendq和recvq是链表结构可是也符合先进先出,在waiq结构中会保存first sudog和last sudog的指针位置,方便进行链表的入队与出队操做)
  2. 若是没有缓冲区,那咱们直接就能够将sudog的数据取出接收。
为何发送的时候不须要判断是否有缓冲区而接收的时候须要判断呢?

咱们能够从接收流程中发现,咱们会在buf为空的时候才会往recvq追加sudog,那么也就是说在接收流程中,recvq只要不为空那就说明buf是空的,那么没有缓冲区和有缓冲区也都是等价于空的buf,因此无需判断。
可是在接收流程中,若是sendq不为空的话。指针

  • 若是有缓冲区,说明buf必定是满的,由于须要维护好前后顺序,因此咱们要维护buf和sendq链表。
  • 没有缓冲区,无需维护buf,因此直接从sendq中找数据内容。
ps:
  • gopark()是挂起的意思,会对应一个goready()唤醒。
  • 挂起与唤醒:code

    • ①sender挂起的必定是由receiver或close唤醒
    • ②receiver挂起的必定是由sender或close唤醒。
相关文章
相关标签/搜索