欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入淺出Golang中select的實現(xiàn)原理

 更新時間:2022年08月17日 09:55:15   作者:yi個俗人  
在go語言中,select語句就是用來監(jiān)聽和channel有關(guān)的IO操作,當(dāng)IO操作發(fā)生時,觸發(fā)相應(yīng)的case操作,有了select語句,可以實現(xiàn)main主線程與goroutine線程之間的互動。本文就來詳細(xì)講講select的實現(xiàn)原理,需要的可以參考一下

概述

在go語言中,select語句就是用來監(jiān)聽和channel有關(guān)的IO操作,當(dāng)IO操作發(fā)生時,觸發(fā)相應(yīng)的case操作,有了select語句,可以實現(xiàn)main主線程與goroutine線程之間的互動。需要的朋友可以參考以下內(nèi)容,希望對大家有幫助。

select實現(xiàn)原理

Golang實現(xiàn)select時,定義了一個數(shù)據(jù)結(jié)構(gòu)表示每個case語句(包含default,default實際上是一種特殊的case),select執(zhí)行過程可以看成一個函數(shù),函數(shù)輸入case數(shù)組,輸出選中的case,然后程序流程轉(zhuǎn)到選中的case塊。

執(zhí)行流程

在默認(rèn)的情況下,select 語句會在編譯階段經(jīng)過如下過程的處理:

  • 將所有的 case 轉(zhuǎn)換成包含 Channel 以及類型等信息的 scase 結(jié)構(gòu)體;
  • 調(diào)用運行時函數(shù) selectgo 獲取被選擇的scase 結(jié)構(gòu)體索引,如果當(dāng)前的 scase 是一個接收數(shù)據(jù)的操作,還會返回一個指示當(dāng)前case 是否是接收的布爾值;
  • 通過 for 循環(huán)生成一組 if 語句,在語句中判斷自己是不是被選中的 case。

case數(shù)據(jù)結(jié)構(gòu)

select控制結(jié)構(gòu)中case使用了scase結(jié)構(gòu)體來表示,源碼包src/runtime/select.go:scase定義了表示case語句的數(shù)據(jù)結(jié)構(gòu):

type scase struct {
    c           *hchan
    elem        unsafe.Pointer
    kind        uint16
    pc          uintptr
    releasetime int64
}

scase.c:由于非defaultcase中都與channel的發(fā)送和接收數(shù)據(jù)有關(guān),所以在scase結(jié)構(gòu)體中也包含一個c字段用于存儲case中使用的channel,為當(dāng)前case語句所操作的channel指針,這也說明了一個case語句只能操作一個channel。

scase.kind:表示該case的類型,分為讀channel、寫channeldefault,三種類型分別由常量定義:

const (
    caseNil = iota
    caseRecv	//case語句中嘗試讀取scase.c中的數(shù)據(jù);
    caseSend	//case語句中嘗試向scase.c中寫入數(shù)據(jù);
    caseDefault //default語句
)

scase.elem:用于接收或者發(fā)送數(shù)據(jù)的變量地址,根據(jù)scase.kind不同,有不同的用途:

  • scase.kind == caseRecv : scase.elem表示讀出channel的數(shù)據(jù)存放地址;
  • scase.kind == caseSend : scase.elem表示將要寫入channel的數(shù)據(jù)存放地址;

執(zhí)行select

在運行期間會調(diào)用selectgo()函數(shù),這個函數(shù)主要作用是從select控制結(jié)構(gòu)中的多個case中選擇一個需要執(zhí)行的case,隨后的多個 if 條件語句就會根據(jù) selectgo() 的返回值執(zhí)行相應(yīng)的語句。

運行時源碼包src/runtime/select.go:selectgo()定義了select選擇case的函數(shù):

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 函數(shù)首先會進行執(zhí)行一些初始化操作,也就是決定處理 case 的兩個順序,其中一個是 pollOrder 另一個是 lockOrder。

函數(shù)參數(shù):

  • cas0:為scase數(shù)組的首地址,selectgo()就是從這些scase中找出一個返回。
  • order0:為一個兩倍cas0數(shù)組長度的buffer,保存scase隨機序列pollorderscasechannel地址序列lockorder;
  • pollorder:每次selectgo執(zhí)行都會把scase序列打亂,以達(dá)到隨機檢測case的目的。
  • lockorder:所有case語句中channel序列,以達(dá)到去重防止對channel加鎖時重復(fù)加鎖的目的。
  • ncases:表示scase數(shù)組的長度

函數(shù)返回值:

  • int: 選中case的編號,這個case編號跟代碼一致
  • bool: 是否成功從channle中讀取了數(shù)據(jù),如果選中的case是從channel中讀數(shù)據(jù),則該返回值表示是否讀取成功。

循環(huán)

當(dāng) select 語句確定了輪詢和鎖定的順序并鎖定了所有的 Channel 之后就會開始進入 select的主循環(huán),查找或者等待 Channel 準(zhǔn)備就緒,循環(huán)中會遍歷所有的 case 并找到需要被喚起的sudog 結(jié)構(gòu)體。

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)

    // ...
}

在這段循環(huán)的代碼中,我們會分四種不同的情況處理 select 中的多個 case

caseNil — 當(dāng)前 case 不包含任何的 Channel,就直接會被跳過;

caseRecv — 當(dāng)前 case 會從 Channel 中接收數(shù)據(jù);

  • 如果當(dāng)前 Channel 的 sendq 上有等待的 Goroutine 就會直接跳到 recv 標(biāo)簽所在的代碼段,從 Goroutine 中獲取最新發(fā)送的數(shù)據(jù);
  • 如果當(dāng)前 Channel 的緩沖區(qū)不為空就會跳到 bufrecv 標(biāo)簽處從緩沖區(qū)中獲取數(shù)據(jù);
  • 如果當(dāng)前 Channel 已經(jīng)被關(guān)閉就會跳到rclose 做一些清除的收尾工作;

caseSend — 當(dāng)前 case 會向 Channel 發(fā)送數(shù)據(jù);

  • 如果當(dāng)前 Channel 已經(jīng)被關(guān)閉就會直接跳到 rclose 代碼段;
  • 如果當(dāng)前 Channel 的 recvq 上有等待的 Goroutine 就會跳到 send 代碼段向 Channel 直接發(fā)送數(shù)據(jù);

caseDefault — 表示默認(rèn)情況,如果循環(huán)執(zhí)行到了這種情況就表示前面的所有case 都沒有被執(zhí)行,所以這里會直接解鎖所有的 Channel 并退出 selectgo 函數(shù),這時也就意味著當(dāng)前 select 結(jié)構(gòu)中的其他收發(fā)語句都是非阻塞的。

總結(jié)

通過以上內(nèi)容我們簡單的了解了select結(jié)構(gòu)的執(zhí)行過程與實現(xiàn)原理,首先在編譯期間,Go 語言會對 select 語句進行優(yōu)化, 對于空的select語句會直接轉(zhuǎn)換成block函數(shù)的調(diào)用,直接掛起當(dāng)前Goroutine,如果select語句中只包含一個case,就會被轉(zhuǎn)換成if ch == nil {block}; n; 表達(dá)式。然后執(zhí)行case結(jié)構(gòu)體中內(nèi)容。

在運行時會執(zhí)行selectgo函數(shù),隨機生成一個遍歷的輪詢順序pollOrder并根據(jù)Channel地址生成一個用于遍歷的鎖定順序lockOrder;然后根據(jù)pollOrder遍歷所有的case查看是否有可以處理的Channel消息,如果有消息就直接獲取case對應(yīng)的索引并返回。如果沒有消息就會創(chuàng)建sudog結(jié)構(gòu)體,將當(dāng)前 Goroutine 加入到所有相關(guān) Channel sendq  recvq 隊列中并調(diào)用 gopark 觸發(fā)調(diào)度器的調(diào)度;

注意: 并不是所有的select控制結(jié)構(gòu)都會走到selectgo,很多情況都會被直接優(yōu)化調(diào)。

以上就是深入淺出Golang中select的實現(xiàn)原理的詳細(xì)內(nèi)容,更多關(guān)于Golang select原理的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • GoFrame?gmap遍歷hashmap?listmap?treemap使用技巧

    GoFrame?gmap遍歷hashmap?listmap?treemap使用技巧

    這篇文章主要為大家介紹了GoFrame?gmap遍歷hashmap?listmap?treemap使用技巧的示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-06-06
  • Go構(gòu)建高性能的命令行工具使例詳解

    Go構(gòu)建高性能的命令行工具使例詳解

    這篇文章主要為大家介紹了Go構(gòu)建高性能的命令行工具使例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-12-12
  • Go語言使用singleflight解決緩存擊穿

    Go語言使用singleflight解決緩存擊穿

    在構(gòu)建高性能的服務(wù)時,緩存是優(yōu)化數(shù)據(jù)庫壓力和提高響應(yīng)速度的關(guān)鍵技術(shù),但使用緩存也會帶來一些問題,其中就包括緩存擊穿,下面我們就來看看Go語言中如何使用singleflight解決緩存擊穿問題吧
    2024-03-03
  • Go語言集成開發(fā)環(huán)境IDE詳細(xì)安裝教程

    Go語言集成開發(fā)環(huán)境IDE詳細(xì)安裝教程

    VSCode是免費開源的現(xiàn)代化輕量級代碼編輯器,支持幾乎所有主流的開發(fā)語言,內(nèi)置命令行工具和 Git 版本控制系統(tǒng),支持插件擴展,這篇文章主要介紹了Go語言集成開發(fā)環(huán)境IDE詳細(xì)安裝教程,需要的朋友可以參考下
    2021-11-11
  • Golang?Gin框架獲取請求參數(shù)的幾種常見方式

    Golang?Gin框架獲取請求參數(shù)的幾種常見方式

    在我們平常添加路由處理函數(shù)之后,就可以在路由處理函數(shù)中編寫業(yè)務(wù)處理代碼了,但在此之前我們往往需要獲取請求參數(shù),本文就詳細(xì)的講解下gin獲取請求參數(shù)常見的幾種方式,需要的朋友可以參考下
    2024-02-02
  • Go編程中常見錯誤和不良實踐解析

    Go編程中常見錯誤和不良實踐解析

    這篇文章主要為大家介紹了Go編程中常見錯誤和不良實踐解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2024-01-01
  • 詳解如何在Go服務(wù)中做鏈路追蹤

    詳解如何在Go服務(wù)中做鏈路追蹤

    使用 Go 語言開發(fā)微服務(wù)的時候,需要追蹤每一個請求的訪問鏈路,本文主要介紹了如何在Go 服務(wù)中做鏈路追蹤,感興趣的可以了解一下
    2021-09-09
  • 使用Go構(gòu)建一款靜態(tài)分析工具Owl詳解

    使用Go構(gòu)建一款靜態(tài)分析工具Owl詳解

    Owl是一款開源項目依賴分析工具,可以快速在指定的項目目錄下查找符合某些特征的源代碼文件或者依賴文件,這篇文章主要介紹了使用Go構(gòu)建一款靜態(tài)分析工具,需要的朋友可以參考下
    2022-06-06
  • Golang字符串變位詞示例詳解

    Golang字符串變位詞示例詳解

    這篇文章主要給大家介紹了關(guān)于GoLang字符串變位詞的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2017-10-10
  • Golang?編寫Tcp服務(wù)器的解決方案

    Golang?編寫Tcp服務(wù)器的解決方案

    Golang?作為廣泛用于服務(wù)端和云計算領(lǐng)域的編程語言,tcp?socket?是其中至關(guān)重要的功能,這篇文章給大家介紹Golang?開發(fā)?Tcp?服務(wù)器及拆包粘包、優(yōu)雅關(guān)閉的解決方案,感興趣的朋友一起看看吧
    2022-10-10

最新評論