亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

概述

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

select實(shí)現(xiàn)原理

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

執(zhí)行流程

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

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

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

select控制結(jié)構(gòu)中case使用了scase結(jié)構(gòu)體來(lái)表示,源碼包src/runtime/select.go:scase定義了表示case語(yǔ)句的數(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)體中也包含一個(gè)c字段用于存儲(chǔ)case中使用的channel,為當(dāng)前case語(yǔ)句所操作的channel指針,這也說(shuō)明了一個(gè)case語(yǔ)句只能操作一個(gè)channel

scase.kind:表示該case的類(lèi)型,分為讀channel、寫(xiě)channeldefault,三種類(lèi)型分別由常量定義:

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

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

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

執(zhí)行select

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

運(yùn)行時(shí)源碼包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ù)首先會(huì)進(jìn)行執(zhí)行一些初始化操作,也就是決定處理 case 的兩個(gè)順序,其中一個(gè)是 pollOrder 另一個(gè)是 lockOrder。

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

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

函數(shù)返回值:

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

循環(huán)

當(dāng) select 語(yǔ)句確定了輪詢(xún)和鎖定的順序并鎖定了所有的 Channel 之后就會(huì)開(kāi)始進(jìn)入 select的主循環(huán),查找或者等待 Channel 準(zhǔn)備就緒,循環(huán)中會(huì)遍歷所有的 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)的代碼中,我們會(huì)分四種不同的情況處理 select 中的多個(gè) case

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

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

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

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

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

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

總結(jié)

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

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

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

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

相關(guān)文章

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

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

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

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

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

    Go語(yǔ)言使用singleflight解決緩存擊穿

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

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

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

    Golang?Gin框架獲取請(qǐng)求參數(shù)的幾種常見(jiàn)方式

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

    Go編程中常見(jiàn)錯(cuò)誤和不良實(shí)踐解析

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

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

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

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

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

    Golang字符串變位詞示例詳解

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

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

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

最新評(píng)論