寫在最前面
select為golang提供了多路IO復用機制,和其他IO復用一樣,用於檢測是否有讀寫事件是否ready。
本文將介紹一下golang的select的用法和實現原理。
實現原理
golang實現select的時候,實際上為每一個case語句定義了一個數據結構,select語句塊執行的時候,實際上可以類比成對一個case數組處理的代碼塊(或者函數),然后程序流程轉到選中的case塊。
case數據結構
源碼包src/runtime/select.go:scase
定義了表示case語句的數據結構:
type scase struct { c *hchan // chan kind uint16 elem unsafe.Pointer // data element }
scase.c表示當前case語句操作的chan指針,這也表明一個case只能監聽一個chan。
scase.kind表示當前的chan是可讀還是可寫channel或者是default。三種類型分別由常量定義:
- caseRecv:case語句中嘗試讀取scase.c中的數據;
- caseSend:case語句中嘗試向scase.c中寫入數據;
- caseDefault: default語句
scase.elem表示緩沖區地址,跟據scase.kind不同,有不同的用途:
- scase.kind == caseRecv : scase.elem表示讀出channel的數據存放地址;
- scase.kind == caseSend : scase.elem表示將要寫入channel的數據存放地址;
select實現邏輯
源碼包src/runtime/select.go:selectgo()
定義了select選擇case的函數:
// selectgo implements the select statement. // // *sel is on the current goroutine's stack (regardless of any // escaping in selectgo). // // selectgo returns the index of the chosen scase, which matches the // ordinal position of its respective select{recv,send,default} call. func selectgo(sel *hselect) int {
}
其中數據結構hselect如下:
// Select statement header. // Known to compiler. // Changes here must also be made in src/cmd/internal/gc/select.go's selecttype. type hselect struct { tcase uint16 // total count of scase[] ncase uint16 // currently filled scase[] pollorder *uint16 // case poll order lockorder *uint16 // channel lock order scase [1]scase // one per case (in order of appearance) }
hselect.tcase存的是scase總數。
hselect.pollorder是保存scase的隨機后的序列。以達到隨機檢測case的目的。
hselect.lockorder是保存的channel地址。所有case語句中channel序列,以達到去重防止對channel加鎖時重復加鎖的目的。
selectgo返回int,表示選中的scase,也就是ready的channel index。
該函數執行邏輯大致如下:
1. 鎖定scase語句中所有的channel
2. 按照隨機順序檢測scase中的channel是否ready
2.1 如果case可讀,則讀取channel中數據,解鎖所有的channel,然后返回(case index)
2.2 如果case可寫,則將數據寫入channel,解鎖所有的channel,然后返回(case index)
2.3 所有case都未ready,則解鎖所有的channel,然后返回(default index)
3. 所有case都未ready,且沒有default語句
3.1 將當前協程加入到所有channel的等待隊列
3.2 當將協程轉入阻塞,等待被喚醒
4. 喚醒后返回channel對應的case index
4.1 如果是讀操作,解鎖所有的channel,然后返回(case index)
4.2 如果是寫操作,解鎖所有的channel,然后返回(case index)