圖解Go select語句原理

RyuGou發表於2019-03-31

Go 的select語句是一種僅能用於channl傳送和接收訊息的專用語句,此語句執行期間是阻塞的;當select中沒有case語句的時候,會阻塞當前的groutine。所以,有人也會說select是用來阻塞監聽goroutine的。 還有人說:select是Golang在語言層面提供的I/O多路複用的機制,其專門用來檢測多個channel是否準備完畢:可讀或可寫。

以上說法都正確。

I/O多路複用

我們來回顧一下是什麼是I/O多路複用

普通多執行緒(或程式)I/O

圖解Go select語句原理

每來一個程式,都會建立連線,然後阻塞,直到接收到資料返回響應。 普通這種方式的缺點其實很明顯:系統需要建立和維護額外的執行緒或程式。因為大多數時候,大部分阻塞的執行緒或程式是處於等待狀態,只有少部分會接收並處理響應,而其餘的都在等待。系統為此還需要多做很多額外的執行緒或者程式的管理工作。

圖解Go select語句原理

為了解決圖中這些多餘的執行緒或者程式,於是有了"I/O多路複用"

I/O多路複用

圖解Go select語句原理

每個執行緒或者程式都先到圖中”裝置“中註冊,然後阻塞,然後只有一個執行緒在”運輸“,當註冊的執行緒或者程式準備好資料後,”裝置“會根據註冊的資訊得到相應的資料。從始至終kernel只會使用圖中這個黃黃的執行緒,無需再對額外的執行緒或者程式進行管理,提升了效率。

select組成結構

select的實現經歷了多個版本的修改,當前版本為:1.11 select這個語句底層實現實際上主要由兩部分組成:case語句執行函式。 原始碼地址為:/go/src/runtime/select.go

每個case語句,單獨抽象出以下結構體:

type scase struct {
    c           *hchan         // chan
    elem        unsafe.Pointer // 讀或者寫的緩衝區地址
    kind        uint16   //case語句的型別,是default、傳值寫資料(channel <-) 還是  取值讀資料(<- channel)
    pc          uintptr // race pc (for race detector / msan)
    releasetime int64
}
複製程式碼

結構體可以用下圖表示:

圖解Go select語句原理
其中比較關鍵的是:hchan,它是channel的指標。 在一個select中,所有的case語句會構成一個scase結構體的陣列。

圖解Go select語句原理

然後執行select語句實際上就是呼叫func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool)函式。

圖解Go select語句原理

func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool)函式引數:

  • cas0 為上文提到的case語句抽象出的結構體scase陣列的第一個元素地址
  • order0為一個兩倍cas0陣列長度的buffer,儲存scase隨機序列pollorder和scase中channel地址序列lockorder。
  • nncases表示scase陣列的長度

selectgo返回所選scase的索引(該索引與其各自的select {recv,send,default}呼叫的序號位置相匹配)。此外,如果選擇的scase是接收操作(recv),則返回是否接收到值。

誰負責呼叫func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool)函式呢?

/reflect/value.go中有個func rselect([]runtimeSelect) (chosen int, recvOK bool)函式,此函式的實現在/runtime/select.go檔案中的func reflect_rselect(cases []runtimeSelect) (int, bool)函式中:

func reflect_rselect(cases []runtimeSelect) (int, bool) { 
    //如果cases語句為空,則阻塞當前groutine
    if len(cases) == 0 {
        block()
    }
    //例項化case的結構體
    sel := make([]scase, len(cases))
    order := make([]uint16, 2*len(cases))
    for i := range cases {
        rc := &cases[i]
        switch rc.dir {
        case selectDefault:
            sel[i] = scase{kind: caseDefault}
        case selectSend:
            sel[i] = scase{kind: caseSend, c: rc.ch, elem: rc.val}
        case selectRecv:
            sel[i] = scase{kind: caseRecv, c: rc.ch, elem: rc.val}
        }
        if raceenabled || msanenabled {
            selectsetpc(&sel[i])
        }
    }
    return selectgo(&sel[0], &order[0], len(cases))
}
複製程式碼

那誰呼叫的func rselect([]runtimeSelect) (chosen int, recvOK bool)呢? 在/refect/value.go中,有一個func Select(cases []SelectCase) (chosen int, recv Value, recvOK bool)的函式,其呼叫了rselect函式,並將最終Go中select語句的返回值的返回。

以上這三個函式的呼叫棧按順序如下:

  • func Select(cases []SelectCase) (chosen int, recv Value, recvOK bool)
  • func rselect([]runtimeSelect) (chosen int, recvOK bool)
  • func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool)

這仨函式中無論是返回值還是引數都大同小異,可以簡單粗暴的認為:函式引數傳入的是case語句,返回值返回被選中的case語句。 那誰呼叫了func Select(cases []SelectCase) (chosen int, recv Value, recvOK bool)呢? 可以簡單的認為是系統了。 來個簡單的圖:

圖解Go select語句原理

前兩個函式Selectrselect都是做了簡單的初始化引數,呼叫下一個函式的操作。select真正的核心功能,是在最後一個函式func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool)中實現的。

selectgo函式做了什麼

打亂傳入的case結構體順序

圖解Go select語句原理

鎖住其中的所有的channel

圖解Go select語句原理

遍歷所有的channel,檢視其是否可讀或者可寫

圖解Go select語句原理

如果其中的channel可讀或者可寫,則解鎖所有channel,並返回對應的channel資料

圖解Go select語句原理

圖解Go select語句原理

假如沒有channel可讀或者可寫,但是有default語句,則同上:返回default語句對應的scase並解鎖所有的channel。

圖解Go select語句原理

假如既沒有channel可讀或者可寫,也沒有default語句,則將當前執行的groutine阻塞,並加入到當前所有channel的等待佇列中去。

圖解Go select語句原理

然後解鎖所有channel,等待被喚醒。

圖解Go select語句原理

此時如果有個channel可讀或者可寫ready了,則喚醒,並再次加鎖所有channel,

圖解Go select語句原理

遍歷所有channel找到那個對應的channel和G,喚醒G,並將沒有成功的G從所有channel的等待佇列中移除。

圖解Go select語句原理

如果對應的scase值不為空,則返回需要的值,並解鎖所有channel

圖解Go select語句原理

如果對應的scase為空,則迴圈此過程。

select和channel之間的關係

在想想select和channel做了什麼事兒,我覺得和多路複用是一回事兒

圖解Go select語句原理

更多精彩內容,請關注我的微信公眾號 網際網路技術窩 或者加微信共同探討交流:

圖解Go select語句原理

參考文獻:

相關文章