go channel 实现原理

先看下源码,源码位于src/runtime/chan.go中

type 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
}
type waitq struct {
    first *sudog
    last  *sudog
}
type sudog struct {
    // The following fields are protected by the hchan.lock of the
    // channel this sudog is blocking on. shrinkstack depends on
    // this for sudogs involved in channel ops.

    g *g

    // isSelect indicates g is participating in a select, so
    // g.selectDone must be CAS'd to win the wake-up race.
    isSelect bool
    next     *sudog
    prev     *sudog
    elem     unsafe.Pointer // data element (may point to stack)

    // The following fields are never accessed concurrently.
    // For channels, waitlink is only accessed by g.
    // For semaphores, all fields (including the ones above)
    // are only accessed when holding a semaRoot lock.

    acquiretime int64
    releasetime int64
    ticket      uint32
    parent      *sudog // semaRoot binary tree
    waitlink    *sudog // g.waiting list or semaRoot
    waittail    *sudog // semaRoot
    c           *hchan // channel
}

qcount uint // 当前队列中剩余元素个数
dataqsiz uint // 环形队列长度,即缓冲区的大小,即make(chan T,N),N.
buf unsafe.Pointer // 环形队列指针
elemsize uint16 // 每个元素的大小
closed uint32 // 表示当前通道是否处于关闭状态。创建通道后,该字段设置为0,即通道打开; 通过调用close将其设置为1,通道关闭。
elemtype *_type // 元素类型,用于数据传递过程中的赋值;
sendx uint和recvx uint是环形缓冲区的状态字段,它指示缓冲区的当前索引 - 支持数组,它可以从中发送数据和接收数据。
recvq waitq // 等待读消息的goroutine队列
sendq waitq // 等待写消息的goroutine队列
lock mutex // 互斥锁,为每个读写操作锁定通道,因为发送和接收必须是互斥操作。
这里sudog代表goroutine。
如创建带缓冲的通道:ch := make(chan int, 3),,底层的数据模型如下图:


image.png

向channel中写入数据
ch <- 3。底层hchan数据流程如图

image.png

image.png

发送操作流程:
1.锁定整个通道结构。
2.确定写入。如果recvq不为空,尝试recvq从等待队列中等待goroutine,然后将元素直接写入goroutine。
3.如果recvq为空,则确定缓冲区是否可用,如果可用,则从当前goroutine复制数据到缓冲区。
4.如果缓冲区已满,则要写入的元素将保存在当前正在执行的goroutine的结构中,并且当前goroutine将在sendq中排队并从运行时挂起。
5.写入完成释放锁。
image.png

从channel中读取数据
ch <- 3,底层hchan数据流程图:
image.png

image.png

读取操作概要:
1.先获取channel全局锁。
2.尝试从sendq等待队列中获取等待的goroutine。
3.如果有等待的goroutine,且没有缓冲区,取出goroutine并读取数据,然后唤醒这个goroutine,结束读取释放锁。
4.如果有等待的goroutine,且有缓冲区(缓冲区已满),从缓冲区队首取出数据,再从sendq中取出一个goroutine,将goroutine中的数据取出存入buf队尾,结束读取释放锁。
5.如果没有等待的goroutine,且缓冲区有数据,直接读取缓冲区数据,结束读取释放锁。
6.如果没有等待的goroutine,且没有缓冲区或缓冲区为空,则当前goroutine加入到recvq排队,进入睡眠,等待被写的goroutine唤醒,结束读取释放锁。
流程图:
image.png

你可能感兴趣的:(go channel 实现原理)