golang的select實現原理剖析


寫在最前面

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)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM