channel分两种, 一种是带缓冲的channel一种是不带缓冲的channel
结构定义
type hchan struct {
//channel队列里面总的数据量
qcount uint // total data in the queue
// 循环队列的容量,如果是非缓冲的channel就是0
dataqsiz uint // size of the circular queue
// 缓冲队列,数组类型。
buf unsafe.Pointer // points to an array of dataqsiz elements
// 元素占用字节的size
elemsize uint16
// 当前队列关闭标志位,非零表示关闭
closed uint32
// 队列里面元素类型
elemtype *_type // element type
// 队列send索引
sendx uint // send index
// 队列索引
recvx uint // receive index
// 等待channel的G队列。
recvq waitq // list of recv waiters
// 向channel发送数据的G队列。
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
}
- 一个数组实现的FIFO,数组有两个下标索引(sendx, recvx)分别表示读写的索引,用于保存channel缓冲区数据
- channel的sendq和recvq队列,队列里面都是持有goroutine的sudog元素,队列都是双链表实现的
- channel的全局锁
buffered channel
当前routine写数据
往buf里写数据
不同goroutine在channel上面进行读写时
写入channel
1. 获取lock
2. 入队
3. 释放lock
读出channel
1. 获取lock
2. 出队
3. 释放lock
写满
1. 当前goroutine(G1)会调用gopark函数,将当前协程置为waiting状态;
2. 将M和G1绑定关系断开;
3. scheduler会调度另外一个就绪态的goroutine与M建立绑定关系,然后M 会运行另外一个G。
从写满channel读取恢复
- G2调用 t:=<-ch 获取一个元素;
- 从channel的buffer里面取出一个元素task1;
- 从sender等待队列里面pop一个sudog;
- 将sender队列里的数据复制buffer中task1的位置,然后更新buffer的sendx和recvx索引值;
- 这时候需要将G1置为Runable状态,表示G1可以恢复运行;
这个时候将G1恢复到可运行状态需要scheduler的参与。G2会调用goready(G1)来唤醒G1
读空channel
- 将recvq中的task存入buffer;
- goready(G2) 唤醒G2;