Golang1.17 学习笔记006
源码地址:runtime/select.go
数据结构:
type scase struct { c *hchan // chan elem unsafe.Pointer // data element }
核心函数:selectgo()
order0 为一个两倍 cas0 数组长度的 buffer,保存 scase 随机序列 pollorder 和 scase 中 channel 地址序列 lockorder
pollorder:每次selectgo执行都会把scase序列打乱,以达到随机检测case的目的
lockorder:所有case语句中channel序列,以达到去重防止对channel加锁时重复加锁的目的
func selectgo(cas0 *scase, order0 *uint16, pc0 *uintptr, nsends, nrecvs int, block bool) (int, bool) { // generate permuted order norder := 0 for i := range scases { cas := &scases[i] // Omit cases without channels from the poll and lock orders. if cas.c == nil { cas.elem = nil // allow GC continue } // .... i++ } pollorder = pollorder[:norder] lockorder = lockorder // sort the cases by Hchan address to get the locking order. // simple heap sort, to guarantee n log n time and constant stack footprint. // 一系列堆排序操作 // lock all the channels involved in the select sellock(scases, lockorder) // pass 1 - look for something already waiting for _, casei := range pollorder { } if !block { selunlock(scases, lockorder) casi = -1 goto retc } // pass 2 - enqueue on all chans gp = getg() if gp.waiting != nil { throw("gp.waiting != nil") } nextp = &gp.waiting for _, casei := range lockorder { } // wait for someone to wake us up gp.param = nil // Signal to anyone trying to shrink our stack that we're about // to park on a channel. The window between when this G's status // changes and when we set gp.activeStackChans is not safe for // stack shrinking. gopark(selparkcommit, nil, waitReasonSelect, traceEvGoBlockSelect, 1) sellock(scases, lockorder) // pass 3 - dequeue from unsuccessful chans otherwise they stack up on quiet channels // record the successful case, if any. We singly-linked up the SudoGs in lock order. // Clear all elem before unlinking from gp.waiting. for sg1 := gp.waiting; sg1 != nil; sg1 = sg1.waitlink { sg1.isSelect = false sg1.elem = nil sg1.c = nil } gp.waiting = nil for _, casei := range lockorder { } } // 伪代码 func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool) { //1. 锁定scase语句中所有的channel //2. 按照随机顺序检测scase中的channel是否ready // 2.1 如果case可读,则读取channel中数据,解锁所有的channel,然后返回(case index, true) // 2.2 如果case可写,则将数据写入channel,解锁所有的channel,然后返回(case index, false) // 2.3 所有case都未ready,则解锁所有的channel,然后返回(default index, false) //3. 所有case都未ready,且没有default语句 // 3.1 将当前协程加入到所有channel的等待队列 // 3.2 当将协程转入阻塞,等待被唤醒 //4. 唤醒后返回channel对应的case index // 4.1 如果是读操作,解锁所有的channel,然后返回(case index, true) // 4.2 如果是写操作,解锁所有的channel,然后返回(case index, false) }
总结
select语句中除default外,各case执行顺序是随机的
select语句中如果没有default语句,则会阻塞等待任一case
参考文献:《Go专家编程》之 select