参考:cloud.tencent.com/developer/a…
channel设计原理:以通信的手段共享内存
channel底层结构:
type hchan struct {
qcount uint // 队列中的数据个数
dataqsiz uint // 环形队列的大小,channel本身是一个环形队列
buf unsafe.Pointer // 存放实际数据的指针,用unsafe.Pointer存放地址,为了避免gc
elemsize uint16
closed uint32 // 标识channel是否关闭
elemtype *_type // 数据 元素类型
sendx uint // send的 index
recvx uint // recv 的 index
recvq waitq // 阻塞在 recv 的队列
sendq waitq // 阻塞在 send 的队列
lock mutex // 锁
}
channel本身有buf、sendx、recx、sendq、recq、lock几部分组成
- buf 是有缓冲的channel特有的数据结构,是一个循环链表,用来储存缓存数据
- sendx、recx用来记录循环链表中发送方和接收方的index
- sendq、recq用来记录接收和发送的goroutine抽象出来的数据
- lock 互斥锁
具体的过程是这样的:
-
当有缓存未满的时候,发送和接收其实都操作buf这个循环链表。
- 加锁
- 取/发送数据
- 解锁
发送数据是把数据放入buf中,接收的过程其实是把数据copy到接收goroutine中,管道的由来
-
如果缓存满了,再进行发送
- 协程是用户态的线程,需要调度器自己去调度。现在会停掉发送方的goroutine并将其放入一个等待队列中,然后m继续执行本地队列中下一个G
- 同时G1会抽象成有G指针和发送元素的sudog结构体,被存入sendq中
-
如果缓存空,进行接收
- 其实过程和缓存满类型,都是G和M解绑,放入等待队列。recq会储存一个G指针和接收元素抽象成sudog结构体
-
如果缓存满之后,再发送,后面又有接收者
- 首先是从buf中取数据copy到接收方
- 对于原本阻塞休眠的G,channel会将其从等待队列中推出,将G当时send的数据放入buf中。而G被唤醒之后,会被放入可运行的goroutine队列中,被调度执行
-
如果缓存空,有接收方接收,然后接收方被放入了recq缓存中,再有发送方发送一个数据
- 这个过程和发送阻塞然后接收类似,都会唤醒原来被阻塞的channel
- 但是发送方的数据不会放到buf中,而是在recq的G唤醒之后,直接copy的recq的G中。这样就省去了一个加锁的操作