網站首頁 編程語言 正文
概述
在go語言中,select
語句就是用來監聽和channel
有關的IO
操作,當IO
操作發生時,觸發相應的case
操作,有了select
語句,可以實現main
主線程與goroutine
線程之間的互動。需要的朋友可以參考以下內容,希望對大家有幫助。
select實現原理
Golang
實現select
時,定義了一個數據結構表示每個case
語句(包含default
,default
實際上是一種特殊的case
),select
執行過程可以看成一個函數,函數輸入case
數組,輸出選中的case
,然后程序流程轉到選中的case
塊。
執行流程
在默認的情況下,select?語句會在編譯階段經過如下過程的處理:
- 將所有的?
case
?轉換成包含Channel
以及類型等信息的?scase?結構體; - 調用運行時函數?
selectgo?
獲取被選擇的scase
?結構體索引,如果當前的?scase?
是一個接收數據的操作,還會返回一個指示當前case
?是否是接收的布爾值; - 通過
?for?
循環生成一組?if?
語句,在語句中判斷自己是不是被選中的?case
。
case數據結構
select
控制結構中case
使用了scase
結構體來表示,源碼包src/runtime/select.go:scase
定義了表示case
語句的數據結構:
type scase struct { c *hchan elem unsafe.Pointer kind uint16 pc uintptr releasetime int64 }
scase.c:由于非default
的case
中都與channel
的發送和接收數據有關,所以在scase
結構體中也包含一個c
字段用于存儲case
中使用的channel
,為當前case
語句所操作的channel
指針,這也說明了一個case
語句只能操作一個channel
。
scase.kind:表示該case的類型,分為讀channel
、寫channel
和default
,三種類型分別由常量定義:
const ( caseNil = iota 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
在運行期間會調用selectgo()
函數,這個函數主要作用是從select
控制結構中的多個case
中選擇一個需要執行的case
,隨后的多個?if?
條件語句就會根據?selectgo()?
的返回值執行相應的語句。
運行時源碼包src/runtime/select.go:selectgo()
定義了select選擇case的函數:
func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool) { cas1 := (*[1 << 16]scase)(unsafe.Pointer(cas0)) order1 := (*[1 << 17]uint16)(unsafe.Pointer(order0)) scases := cas1[:ncases:ncases] pollorder := order1[:ncases:ncases] lockorder := order1[ncases:][:ncases:ncases] for i := range scases { cas := &scases[i] if cas.c == nil && cas.kind != caseDefault { *cas = scase{} } } for i := 1; i < ncases; i++ { j := fastrandn(uint32(i + 1)) pollorder[i] = pollorder[j] pollorder[j] = uint16(i) } // sort the cases by Hchan address to get the locking order. // ... sellock(scases, lockorder) // ... }
selectgo
?函數首先會進行執行一些初始化操作,也就是決定處理?case?
的兩個順序,其中一個是?pollOrder?
另一個是?lockOrder
。
函數參數:
- cas0:為scase數組的首地址,
selectgo()
就是從這些scase
中找出一個返回。 - order0:為一個兩倍cas0數組長度的buffer,保存scase隨機序列
pollorder
和scase
中channel
地址序列lockorder
; - pollorder:每次
selectgo
執行都會把scase
序列打亂,以達到隨機檢測case
的目的。 - lockorder:所有
case
語句中channel
序列,以達到去重防止對channel
加鎖時重復加鎖的目的。 - ncases:表示
scase
數組的長度
函數返回值:
- int: 選中
case
的編號,這個case
編號跟代碼一致 - bool: 是否成功從
channle
中讀取了數據,如果選中的case
是從channel
中讀數據,則該返回值表示是否讀取成功。
循環
當?select?語句確定了輪詢和鎖定的順序并鎖定了所有的 Channel
之后就會開始進入?select
的主循環,查找或者等待 Channel
準備就緒,循環中會遍歷所有的?case
?并找到需要被喚起的sudog?
結構體。
func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool) { // ... gp = getg() 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 sg.elem = cas.elem sg.c = c *nextp = sg nextp = &sg.waitlink switch cas.kind { case caseRecv: c.recvq.enqueue(sg) case caseSend: c.sendq.enqueue(sg) } } gp.param = nil gopark(selparkcommit, nil, waitReasonSelect, traceEvGoBlockSelect, 1) // ... }
在這段循環的代碼中,我們會分四種不同的情況處理?select?
中的多個?case
:
caseNil?— 當前?case
?不包含任何的 Channel
,就直接會被跳過;
caseRecv?— 當前?case
?會從 Channel
中接收數據;
- 如果當前
Channel
的?sendq
?上有等待的Goroutine
就會直接跳到?recv
?標簽所在的代碼段,從Goroutine
中獲取最新發送的數據; - 如果當前
Channel
的緩沖區不為空就會跳到?bufrecv?
標簽處從緩沖區中獲取數據; - 如果當前
Channel
已經被關閉就會跳到rclose
?做一些清除的收尾工作;
caseSend?— 當前?case?
會向 Channel
發送數據;
- 如果當前
Channel
已經被關閉就會直接跳到?rclose?
代碼段; - 如果當前
Channel
的?recvq
?上有等待的Goroutine
就會跳到?send?
代碼段向Channel
直接發送數據;
caseDefault?— 表示默認情況,如果循環執行到了這種情況就表示前面的所有case?
都沒有被執行,所以這里會直接解鎖所有的 Channel
并退出?selectgo
?函數,這時也就意味著當前?select?
結構中的其他收發語句都是非阻塞的。
總結
通過以上內容我們簡單的了解了select
結構的執行過程與實現原理,首先在編譯期間,Go
語言會對?select?
語句進行優化, 對于空的select
語句會直接轉換成block
函數的調用,直接掛起當前Goroutine
,如果select
語句中只包含一個case
,就會被轉換成if ch == nil {block}; n;
表達式。然后執行case結構體中內容。
在運行時會執行selectgo
函數,隨機生成一個遍歷的輪詢順序pollOrder
并根據Channel
地址生成一個用于遍歷的鎖定順序lockOrder
;然后根據pollOrder
遍歷所有的case
查看是否有可以處理的Channel
消息,如果有消息就直接獲取case對應的索引并返回。如果沒有消息就會創建sudog結構體,將當前 Goroutine
加入到所有相關 Channel
的sendq?
和?recvq?
隊列中并調用?gopark?
觸發調度器的調度;
注意: 并不是所有的select控制結構都會走到selectgo
,很多情況都會被直接優化調。
原文鏈接:https://juejin.cn/post/7132355757944619039
相關推薦
- 2022-08-27 C#從前面或后面按指定數量刪除字符串_C#教程
- 2022-09-16 Python中的?No?Module?named?***問題及解決_python
- 2022-06-15 ASP.NET?MVC使用區域(Area)功能_基礎應用
- 2022-04-25 JQuery實現Table的tr上移下移功能_jquery
- 2022-10-27 Python?Opencv實戰之文字檢測OCR_python
- 2022-06-12 超詳細分析C語言動態內存管理問題_C 語言
- 2022-10-16 QT網絡通信TCP客戶端實現詳解_C 語言
- 2023-01-17 怎樣保存模型權重和checkpoint_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支