select的作用
golang 中的 select 就是用來監聽和 channel 有關的 IO 操作,當 IO 操作發生時,觸發相應的動作。select 只能應用於 channel 的操作,既可以用於 channel 的數據接收,也可以用於 channel 的數據發送。如果 select 的多個分支都滿足條件,則會隨機的選取其中一個滿足條件的分支執行。
基本用法
select { case <- chan1: // 如果 chan1 成功讀到數據,則進行該 case 處理語句 case chan2 <- 1: // 如果成功向 chan2 寫入數據,則進行該 case 處理語句 default: // 如果上面都沒有成功,則進入default處理流程 }
select原理
select結構
在默認的情況下,select 語句會在編譯階段經過如下過程的處理:
- 將所有的
case
轉換成包含Channel
以及類型等信息的 scase 結構體; - 調用運行時函數
selectgo
獲取被選擇的scase
結構體索引,如果當前的scase
是一個接收數據的操作,還會返回一個指示當前case
是否是接收的布爾值; - 通過
for
循環生成一組if
語句,在語句中判斷自己是不是被選中的case
。
runtime\select.go
type scase struct { c *hchan // 當前case語句所操作的channel指針 elem unsafe.Pointer // data element數據元素 kind uint16 //表示該case的類型 pc uintptr // race pc (for race detector / msan) releasetime int64 }
//scase.kind values. const ( caseNil = iota caseRecv caseSend caseDefault )
scase.kind表示該case的類型,分別爲:
- caseRecv:case語句中嘗試讀取scase.c中的數據,case <-Chan;
- caseSend:case語句中嘗試向scase.c中寫入數據,case Chan <- Send;
- caseDefault:default
執行select
在運行期間會調用selectgo()
函數,這個函數主要作用是從select
控制結構中的多個case
中選擇一個需要執行的case
,隨後的多個 if
條件語句就會根據 selectgo()
的返回值執行相應的語句。
運行時源碼包src/runtime/select.go:selectgo()
定義了select選擇case的函數:
// cas0爲scase數組的首地址,selectgo()就是從這些scase中找出一個返回 // order0指向一個[2 * ncases] uint16類型的數組 // ncases表示scase數組的長度 //返回值: //int :選中case的編號 //bool:是否成功從channle中讀取了數據 func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool) { if debugSelect { print("select: cas0=", cas0, "\n") } cas1 := (*[1 << 16]scase)(unsafe.Pointer(cas0)) order1 := (*[1 << 17]uint16)(unsafe.Pointer(order0)) //將cas1從第一個元素開始切片,長度爲ncases,容量爲ncases //a[x:y:z] 切片長度: y-x 切片容量:z-x scases := cas1[:ncases:ncases] //所有case輪詢順序,佔了前面ncase pollorder := order1[:ncases:ncases] //所有case語句中channel序列,佔了後面ncase lockorder := order1[ncases:][:ncases:ncases] // 將涉及零個通道的發送/接收案例替換爲caseNil,因此以下邏輯可以假定爲非nil通道。 for i := range scases { cas := &scases[i] if cas.c == nil && cas.kind != caseDefault { *cas = scase{} } } var t0 int64 if blockprofilerate > 0 { t0 = cputicks() for i := 0; i < ncases; i++ { scases[i].releasetime = -1 } } // 生成排列順序,pollorder重新排序 for i := 1; i < ncases; i++ { j := fastrandn(uint32(i + 1)) pollorder[i] = pollorder[j] pollorder[j] = uint16(i) } //通過Hchan地址進行排序以獲得鎖定順序 //採用簡單的堆排序,以確保n log n個時間和恆定的堆棧佔用量 for i := 0; i < ncases; i++ { j := i // 替換相同channel 的case,以達到去重防止對channel加鎖時重複加鎖的目的 c := scases[pollorder[i]].c for j > 0 && scases[lockorder[(j-1)/2]].c.sortkey() < c.sortkey() { k := (j - 1) / 2 lockorder[j] = lockorder[k] j = k } lockorder[j] = pollorder[i] } for i := ncases - 1; i >= 0; i-- { o := lockorder[i] c := scases[o].c lockorder[i] = lockorder[0] j := 0 for { k := j*2 + 1 if k >= i { break } if k+1 < i && scases[lockorder[k]].c.sortkey() < scases[lockorder[k+1]].c.sortkey() { k++ } if c.sortkey() < scases[lockorder[k]].c.sortkey() { lockorder[j] = lockorder[k] j = k continue } break } lockorder[j] = o } if debugSelect { for i := 0; i+1 < ncases; i++ { if scases[lockorder[i]].c.sortkey() > scases[lockorder[i+1]].c.sortkey() { print("i=", i, " x=", lockorder[i], " y=", lockorder[i+1], "\n") throw("select: broken sort") } } } //鎖住所有的channel sellock(scases, lockorder) var ( gp *g sg *sudog c *hchan k *scase sglist *sudog sgnext *sudog qp unsafe.Pointer nextp **sudog ) loop: // pass 1 - look for something already waiting // 按照隨機順序檢測scase中的channel是否ready var dfli int var dfl *scase var casi int var cas *scase var recvOK bool //開始遍歷case數組了 for i := 0; i < ncases; i++ { casi = int(pollorder[i]) cas = &scases[casi] c = cas.c switch cas.kind { case caseNil: continue // 接收chan case caseRecv: sg = c.sendq.dequeue() // 當chan的等待寫隊列不爲空,需要等待 if sg != nil { goto recv } //當chan的緩存隊列存在元素時,不需要等待 if c.qcount > 0 { goto bufrecv } // 當chan關閉時 if c.closed != 0 { goto rclose } //發送chan case caseSend: if raceenabled { racereadpc(c.raceaddr(), cas.pc, chansendpc) } // 當chan關閉時 if c.closed != 0 { goto sclose } //當chan的等待讀消息的隊列不爲空,需要等待 sg = c.recvq.dequeue() if sg != nil { goto send } // chan的緩存隊列的元素少於緩存容量時,還有位置,不需要等待 if c.qcount < c.dataqsiz { goto bufsend } case caseDefault: dfli = casi dfl = cas } } if dfl != nil { selunlock(scases, lockorder) casi = dfli cas = dfl goto retc } // pass 2 - enqueue on all chans //所有case都未ready,且沒有default語句 //將當前協程加入到所有channel的等待隊列 gp = getg() if gp.waiting != nil { throw("gp.waiting != nil") } nextp = &gp.waiting for _, casei := range lockorder { casi = int(casei) cas = &scases[casi] if cas.kind == caseNil { continue } c = cas.c sg := acquireSudog() sg.g = gp sg.isSelect = true // No stack splits between assigning elem and enqueuing // sg on gp.waiting where copystack can find it. sg.elem = cas.elem sg.releasetime = 0 if t0 != 0 { sg.releasetime = -1 } sg.c = c // Construct waiting list in lock order. *nextp = sg nextp = &sg.waitlink switch cas.kind { case caseRecv: // 加入等待接收隊列 c.recvq.enqueue(sg) case caseSend: // 加入等待發送隊列 c.sendq.enqueue(sg) } } // wait for someone to wake us up //當將協程轉入阻塞,等待被喚醒 gp.param = nil gopark(selparkcommit, nil, waitReasonSelect, traceEvGoBlockSelect, 1) sellock(scases, lockorder) gp.selectDone = 0 sg = (*sudog)(gp.param) gp.param = nil // 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. //喚醒後返回channel對應的case index casi = -1 cas = nil sglist = gp.waiting // 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 { k = &scases[casei] if k.kind == caseNil { continue } if sglist.releasetime > 0 { k.releasetime = sglist.releasetime } if sg == sglist { // sg has already been dequeued by the G that woke us up. casi = int(casei) cas = k } else { c = k.c if k.kind == caseSend { c.sendq.dequeueSudoG(sglist) } else { c.recvq.dequeueSudoG(sglist) } } sgnext = sglist.waitlink sglist.waitlink = nil //釋放所有的鎖 releaseSudog(sglist) sglist = sgnext } //沒找到case,重新循環 if cas == nil { goto loop } c = cas.c if debugSelect { print("wait-return: cas0=", cas0, " c=", c, " cas=", cas, " kind=", cas.kind, "\n") } if cas.kind == caseRecv { recvOK = true } if raceenabled { if cas.kind == caseRecv && cas.elem != nil { raceWriteObjectPC(c.elemtype, cas.elem, cas.pc, chanrecvpc) } else if cas.kind == caseSend { raceReadObjectPC(c.elemtype, cas.elem, cas.pc, chansendpc) } } if msanenabled { if cas.kind == caseRecv && cas.elem != nil { msanwrite(cas.elem, c.elemtype.size) } else if cas.kind == caseSend { msanread(cas.elem, c.elemtype.size) } } selunlock(scases, lockorder) goto retc bufrecv: // 可以從緩衝區接收 if raceenabled { if cas.elem != nil { raceWriteObjectPC(c.elemtype, cas.elem, cas.pc, chanrecvpc) } raceacquire(chanbuf(c, c.recvx)) racerelease(chanbuf(c, c.recvx)) } if msanenabled && cas.elem != nil { msanwrite(cas.elem, c.elemtype.size) } recvOK = true qp = chanbuf(c, c.recvx) if cas.elem != nil { // 將chan緩存中的數據拷貝到 case.elem。 eg: a := <-ch, a就是case.elem typedmemmove(c.elemtype, cas.elem, qp) } typedmemclr(c.elemtype, qp) c.recvx++ if c.recvx == c.dataqsiz { c.recvx = 0 } c.qcount-- selunlock(scases, lockorder) goto retc bufsend: //可以發送到緩衝區 if raceenabled { raceacquire(chanbuf(c, c.sendx)) racerelease(chanbuf(c, c.sendx)) raceReadObjectPC(c.elemtype, cas.elem, cas.pc, chansendpc) } if msanenabled { msanread(cas.elem, c.elemtype.size) } // 將cas.elem拷貝到chan的緩存中。eg: ch <- a, a 就是 cas.elem typedmemmove(c.elemtype, chanbuf(c, c.sendx), cas.elem) c.sendx++ if c.sendx == c.dataqsiz { c.sendx = 0 } c.qcount++ selunlock(scases, lockorder) goto retc recv: //可以從休眠的發件人(sg)接收 recv(c, sg, cas.elem, func() { selunlock(scases, lockorder) }, 2) if debugSelect { print("syncrecv: cas0=", cas0, " c=", c, "\n") } recvOK = true goto retc rclose: //在封閉channel的末尾讀取 selunlock(scases, lockorder) recvOK = false if cas.elem != nil { typedmemclr(c.elemtype, cas.elem) } if raceenabled { raceacquire(c.raceaddr()) } goto retc send: //可以發送到休眠的接收器(sg) if raceenabled { raceReadObjectPC(c.elemtype, cas.elem, cas.pc, chansendpc) } if msanenabled { msanread(cas.elem, c.elemtype.size) } send(c, sg, cas.elem, func() { selunlock(scases, lockorder) }, 2) if debugSelect { print("syncsend: cas0=", cas0, " c=", c, "\n") } goto retc retc: if cas.releasetime > 0 { blockevent(cas.releasetime-t0, 1) } return casi, recvOK sclose: //在關閉的channel上發送 selunlock(scases, lockorder) panic(plainError("send on closed channel")) }
1.初始化pollorder切片和lockorder切片,其中pollorder是存放所有case的,lockorder是存放所有channel
2.對pollorder進行重排序,打亂所有case的順序
3.鎖定scase語句中所有的channel
4.按照隨機順序檢測pollorder中case對應的channel是否ready:
4.1 如果case可讀,則讀取channel中數據,解鎖所有的channel,然後返回(case index, true)
4.2 如果case可寫,則將數據寫入channel,解鎖所有的channel,然後返回(case index, false)
4.3 所有case都未ready,則解鎖所有的channel,然後返回(default index, false)
5.所有case都未ready,且沒有default語句
5.1 將當前協程加入到所有channel的等待隊列
5.2 當將協程轉入阻塞,等待被喚醒
6.喚醒後返回channel對應的case index
6.1 如果是讀操作,解鎖所有的channel,然後返回(case index, true)
6.2 如果是寫操作,解鎖所有的channel,然後返回(case index, false)總結
select語句中除default外,各case執行順序是隨機的
select語句中如果沒有default語句,則會阻塞等待任意一個case
select語句中讀操作要判斷是否成功讀取,關閉的channel也可以讀取
select語句中除default外,每個case只能操作一個channel,要麼讀要麼寫