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 写数据的流程:如果等待接收队列 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 是否已经关闭的接口。