目录

go channel

底层结构

    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
    }

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 // 互斥锁,为每个读写操作锁定通道,因为发送和接收必须是互斥操作。

各个状态下操作结果

./channel各状态操作结果.png

发送接收流程

向 channel 写数据的流程:如果等待接收队列 recvq 不为空,说明缓冲区中没有数据或者没有缓冲区,此时直接从 recvq 取出 G,并把数据写入,最后把该 G 唤醒,结束发送过程;如果缓冲区中有空余位置,将数据写入缓冲区,结束发送过程;如果缓冲区中没有空余位置,将待发送数据写入 G,将当前 G 加入 sendq,进入睡眠,等待被读 goroutine 唤醒;

向 channel 读数据的流程:如果等待发送队列 sendq 不为空,且没有缓冲区,直接从 sendq 中取出 G,把 G 中数据读出,最后把 G 唤醒,结束读取过程;如果等待发送队列 sendq 不为空,此时说明缓冲区已满,从缓冲区中首部读出数据,把 G 中数据写入缓冲区尾部,把 G 唤醒,结束读取过程;如果缓冲区中有数据,则从缓冲区取出数据,结束读取过程;将当前 goroutine 加入 recvq,进入睡眠,等待被写 goroutine 唤醒;

其他

关闭不再需要使用的 channel 并不是必须的。跟其他资源比如打开的文件、socket 连接不一样,这类资源使用完后不关闭后会造成句柄泄露,channel 使用完后不关闭也没有关系,channel 没有被任何协程用到后最终会被 GC 回收。关闭 channel 一般是用来通知其他协程某个任务已经完成了。golang 也没有直接提供判断 channel 是否已经关闭的接口。