channel
hchan结构
1channel通过make创建后在栈上存指针,数据结构存储在堆上
2 lock–channel要支持并发访问,所以要有锁
2 buf–有缓冲区的channel,需要知道缓冲区的地址
3 qcount–channel已经存储了多少个元素
4 datesize–最多能存储多少个元素
5 slemsize–每个元素占多大空间
6 elemtype–指针,指向元素的元类型
7 sendx,rcvx–读和写的下标位置
8 recvq sendq–读和写队列
9 close–是否close
sudog结构
1当一个goroutine g1向channel发送数据1-6,发送到第五个channel缓冲区已满,g1进入sendq队列等待
2sudog结构体中的g记录是哪一个g在等待,elem记录等待的元素是什么,hchan记录是哪一个channel
1当有另一个g2来读取数据,下标为0的数据被拿走,buf有空间,g1的元素6写入,g1从sendq队列移除
2 sendx和recvx都会从0-4循环变化,这杯称为环形缓冲区
多路select
1select实际是调用runtime.selectgo()的调用
2 cas0—数组,里面是所有的case分支,send在前,recv在后(以case聚合)
3 order0—数组,分两部分,第一部分是所有channel的轮询乱序,第二个数组是枷锁顺序
4 nrecvs nsends----所有case操作中执行send和recv的操作分别有多少个(以send和recv聚合)
5 block—是否阻塞,对应到代码中就是有default不会阻塞,没有default则阻塞
6 返回值int----标识最终那个case分支被执行了,对应到cas0数组的下标,如果是default分支则是-1
7 返回值bool—标识recv操作时,是实际接受到了一个值,还是因channel关闭而接收到了0值
1goroutine执行channel时,会以乱序检测那个case分支可以执行了,但会先按照顺序对所有channel枷锁,乱序检查所有channel的等待队列(recvq,sendq)和缓冲区(buf)
2 加入检测ch1缓冲区有数据可读,就拷贝数据进入对应case分支
3假如ch1 recvq没空,ch2sendq已满,标识没有可执行的case,则g1会被添加到ch1的recvq和ch2的sendq中,g1倍挂起
4 g1挂起后解锁所有是channel
4 加入ch1有数据可读,g1再次唤醒,完成对应的case分支
5 按序枷锁
6 g1将自己从等待队列中(sendq,recvq)移除
7 全部解锁channel,返回
sudoG 结构体:
// sudog 代表在等待列表里的 g,比如向 channel 发送/接收内容时
// 之所以需要 sudog 是因为 g 和同步对象之间的关系是多对多的
// 一个 g 可能会在多个等待队列中,所以一个 g 可能被打包为多个 sudog
// 多个 g 也可以等待在同一个同步对象上
// 因此对于一个同步对象就会有很多 sudog 了
// sudog 是从一个特殊的池中进行分配的。用 acquireSudog 和 releaseSudog 来分配和释放 sudog
send
不阻塞—执行case分支
阻塞------执行default分支
rcv
send和recv操作底层是调用了euntime.chansend()和runtime.chanrecv()
runtime等待队列-semaphore信号量
多核–锁住总线
解决锁定问题:
1
1 通过结构体中记录数值的变量sema计算尺地址映射到sematable上
2 通过地址找到对应节点,就找到了这个节点所在的等待队列
通过sync.Mutex.sema计算映射daosematable的index,就找到了根节点,再在设个平衡树上找到对应节点的等待队列
semaphore和channel的等待队列底层都是mutex