首先看一下它的结构体
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,接收goroutine链表
sendq waitq // list of send waiters,发送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 //锁
}
1.有lock字段就说明channe是线程安全的
2.recvq,sendq都是goroutine链表的封装,recvq表示这个channel的多个接收goroutine,sendq表示这个channel的多个发送goroutine,在发送数据或者关闭channel的时候会用到这两个字段
3.qcount,dataqsiz,buf,sendx,recvx 这几个字段都是和缓冲channel有关的,sendx,recvx会在发送数据和接收数据的时候会用到这两个字段
发送数据原理:
1.发送数据
2.轮训检查recvq链表中是否有接收的goroutine,如果有,直接就把这个发送的值拷贝到接收goroutine的栈里直接返回,如果没有进行下一步
3.如果是一个带缓冲区的channel,检查容量是否已满,不满则根据sendx插入到缓冲数组中,直接返回,已满则该协程阻塞
接收数据原理:
1.检查channel是否已经关闭,关闭了就直接跳到第3步,没关闭就第2步
2.轮训检查sendq链表中是否有发送的goroutine,如果有,直接就把这个发送的值拷贝到接收goroutine的栈里直接返回,如果没有进行下一步
2.如果是一个带缓冲区的channel,检查循环数组中是否有可以发送的元素,有则根据recvx将元素拷贝到该goroutine栈中,直接返回,没有可放出的元素则该协程阻塞
所以仍然可以从一个已经关闭的带有缓冲区的channel读取出数据
如果一个channel被多个goroutine监听,则数据会随机被某个goroutine消费
总的来说,channe 协程间通信的本质是拷贝,优先是recvq,sendq与当前协程之间直接的数据拷贝,若存在缓冲区,则还有缓冲区和协程之间的拷贝
关闭channel时发生了什么?
先将closed赋值为1
加一把大锁【遍历所有的recvq,sendq加入到一个全部协程池】解锁,唤醒全部的协程,所有sendq协程直接panic,所有recvq返回默认值
网友评论