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

一文帶你了解Golang中select的實現(xiàn)原理

 更新時間:2023年02月19日 09:37:55   作者:nil  
select是go提供的一種跟并發(fā)相關(guān)的語法,非常有用。本文將介紹?Go?語言中的?select?的實現(xiàn)原理,包括?select?的結(jié)構(gòu)和常見問題、編譯期間的多種優(yōu)化以及運行時的執(zhí)行過程

概述

select是go提供的一種跟并發(fā)相關(guān)的語法,非常有用。本文將介紹 Go 語言中的 select 的實現(xiàn)原理,包括 select 的結(jié)構(gòu)和常見問題、編譯期間的多種優(yōu)化以及運行時的執(zhí)行過程。

select 是一種與 switch 非常相似的控制結(jié)構(gòu),與 switch 不同的是,select 中雖然也有多個 case,但是這些 case 中的表達(dá)式都必須與 Channel 的操作有關(guān),也就是 Channel 的讀寫操作,下面的函數(shù)就展示了一個包含從 Channel 中讀取數(shù)據(jù)和向 Channel 發(fā)送數(shù)據(jù)的 select 結(jié)構(gòu):

func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for {
        select {
        case c <- x:
            x, y = y, x+y
        case <-quit:
            fmt.Println("quit")
            return
        }
    }
}

這個 select 控制結(jié)構(gòu)就會等待 c <- x 或者 <-quit 兩個表達(dá)式中任意一個的返回,無論哪一個返回都會立刻執(zhí)行 case 中的代碼,不過如果了 select 中的兩個 case 同時被觸發(fā),就會隨機選擇一個 case 執(zhí)行。

結(jié)構(gòu)

select 在 Go 語言的源代碼中其實不存在任何的結(jié)構(gòu)體表示,但是 select 控制結(jié)構(gòu)中 case 卻使用了 scase 結(jié)構(gòu)體來表示:

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

由于非 default 的 case 中都與 Channel 的發(fā)送和接收數(shù)據(jù)有關(guān),所以在 scase 結(jié)構(gòu)體中也包含一個 c 字段用于存儲 case 中使用的 Channel,elem 是用于接收或者發(fā)送數(shù)據(jù)的變量地址、kind 表示當(dāng)前 case 的種類,總共包含以下四種:

const (
    caseNil = iota
    caseRecv
    caseSend
    caseDefault
)

這四種常量分別表示不同類型的 case,相信它們的命名已經(jīng)能夠充分幫助我們理解它們的作用了,所以在這里也不再展開介紹了。

現(xiàn)象

當(dāng)我們在 Go 語言中使用 select 控制結(jié)構(gòu)時,其實會遇到兩個非常有趣的問題,一個是 select 能在 Channel 上進(jìn)行非阻塞的收發(fā)操作,另一個是 select 在遇到多個 Channel 同時響應(yīng)時能夠隨機挑選 case 執(zhí)行。

非阻塞的收發(fā)

如果一個 select 控制結(jié)構(gòu)中包含一個 default 表達(dá)式,那么這個 select 并不會等待其它的 Channel 準(zhǔn)備就緒,而是會非阻塞地讀取或者寫入數(shù)據(jù):

func main() {
    ch := make(chan int)
    select {
    case i := <-ch:
        println(i)

    default:
        println("default")
    }
}

$ go run main.go
default

當(dāng)我們運行上面的代碼時其實也并不會阻塞當(dāng)前的 Goroutine,而是會直接執(zhí)行 default 條件中的內(nèi)容并返回。

隨機執(zhí)行

另一個使用 select 遇到的情況其實就是同時有多個 case 就緒后,select 如何進(jìn)行選擇的問題,我們通過下面的代碼可以簡單了解一下:

func main() {
    ch := make(chan int)
    go func() {
        for range time.Tick(1 * time.Second) {
            ch <- 0
        }
    }()

    for {
        select {
        case <-ch:
            println("case1")

        case <-ch:
            println("case2")
        }
    }
}

$ go run main.go
case1
case2
case1
case2
case2
case1
...

從上述代碼輸出的結(jié)果中我們可以看到,select 在遇到兩個 <-ch 同時響應(yīng)時其實會隨機選擇一個 case 執(zhí)行其中的表達(dá)式,我們會在這一節(jié)中介紹這一現(xiàn)象的實現(xiàn)原理。

編譯

select 語句在編譯期間會被轉(zhuǎn)換成 OSELECT 節(jié)點,每一個 OSELECT 節(jié)點都會持有一系列的 OCASE 節(jié)點,如果 OCASE 節(jié)點的都是空的,就意味著這是一個 default 節(jié)點:

上圖展示的其實就是 select 在編譯期間的結(jié)構(gòu),每一個 OCASE 既包含了執(zhí)行條件也包含了滿足條件后執(zhí)行的代碼,我們在這一節(jié)中就會介紹 select 語句在編譯期間進(jìn)行的優(yōu)化和轉(zhuǎn)換。

編譯器在中間代碼生成期間會根據(jù) select 中 case 的不同對控制語句進(jìn)行優(yōu)化,這一過程其實都發(fā)生在 walkselectcases 函數(shù)中,我們在這里會分四種情況分別介紹優(yōu)化的過程和結(jié)果:

select 中不存在任何的 case;

select 中只存在一個 case;

select 中存在兩個 case,其中一個 case 是 default 語句;

通用的 select 條件;

我們會按照這四種不同的情況拆分 walkselectcases 函數(shù)并分別介紹不同場景下優(yōu)化的結(jié)果。

直接阻塞

首先介紹的其實就是最簡單的情況,也就是當(dāng) select 結(jié)構(gòu)中不包含任何的 case 時,編譯器是如何進(jìn)行處理的:

func walkselectcases(cases *Nodes) []*Node {
    n := cases.Len()

    if n == 0 {
        return []*Node{mkcall("block", nil, nil)}
    }
    // ...
}

這段代碼非常簡單并且容易理解,它直接將類似 select {} 的空語句,轉(zhuǎn)換成對 block 函數(shù)的調(diào)用:

func block() {
    gopark(nil, nil, waitReasonSelectNoCases, traceEvGoStop, 1)
}

block 函數(shù)的實現(xiàn)非常簡單,它會運行 gopark 讓出當(dāng)前 Goroutine 對處理器的使用權(quán),該 Goroutine 也會進(jìn)入永久休眠的狀態(tài)也沒有辦法被其他的 Goroutine 喚醒,我們可以看到調(diào)用 gopark 方法時傳入的等待原因是 waitReasonSelectNoCases,這其實也在告訴我們一個空的 select 語句會直接阻塞當(dāng)前的 Goroutine。

獨立情況

如果當(dāng)前的 select 條件只包含一個 case,那么就會就會執(zhí)行如下的優(yōu)化策略將原來的 select 語句改寫成 if 條件語句,下面是在 select 中從 Channel 接受數(shù)據(jù)時被改寫的情況:

select {
case v, ok <-ch:
    // ...    
}

if ch == nil {
    block()
}
v, ok := <-ch
// ...

在 walkselectcases 函數(shù)中,如果只包含一個發(fā)送的 case,那么就不會包含 v, ok := <- ch 這個表達(dá)式,因為向 Channel 發(fā)送數(shù)據(jù)并沒有任何的返回值。

我們可以看到如果在 select 中僅存在一個 case,那么當(dāng) case 中處理的 Channel 是空指針時,就會發(fā)生和沒有 case 的 select 語句一樣的情況,也就是直接掛起當(dāng)前 Goroutine 并且永遠(yuǎn)不會被喚醒。

非阻塞操作

在下一次的優(yōu)化策略執(zhí)行之前,walkselectcases 函數(shù)會先將 case 中所有 Channel 都轉(zhuǎn)換成指向 Channel 的地址以便于接下來的優(yōu)化和通用邏輯的執(zhí)行,改寫之后就會進(jìn)行最后一次的代碼優(yōu)化,觸發(fā)的條件就是 — select 中包含兩個 case,但是其中一個是 default,我們可以分成發(fā)送和接收兩種情況介紹處理的過程。

發(fā)送

首先就是 Channel 的發(fā)送過程,也就是 case 中的表達(dá)式是 OSEND 類型,在這種情況下會使用 if/else 語句改寫代碼:

select {
case ch <- i:
    // ...
default:
    // ...
}

if selectnbsend(ch, i) {
    // ...
} else {
    // ...
}

這里最重要的函數(shù)其實就是 selectnbsend,它的主要作用就是非阻塞地向 Channel 中發(fā)送數(shù)據(jù),我們在 Channel 一節(jié)曾經(jīng)提到過發(fā)送數(shù)據(jù)的 chansend 函數(shù)包含一個 block 參數(shù),這個參數(shù)會決定這一次的發(fā)送是不是阻塞的:

func selectnbsend(c *hchan, elem unsafe.Pointer) (selected bool) {
    return chansend(c, elem, false, getcallerpc())
}

在這里我們只需要知道當(dāng)前的發(fā)送過程不是阻塞的,哪怕是沒有接收方、緩沖區(qū)空間不足導(dǎo)致失敗了也會立即返回。

接收

由于從 Channel 中接收數(shù)據(jù)可能會返回一個或者兩個值,所以這里的情況會比發(fā)送時稍顯復(fù)雜,不過改寫的套路和邏輯確是差不多的:

select {
case v <- ch: // case v, received <- ch:
    // ...
default:
    // ...
}

if selectnbrecv(&v, ch) { // if selectnbrecv2(&v, &received, ch) {
    // ...
} else {
    // ...
}

返回值數(shù)量不同會導(dǎo)致最終使用函數(shù)的不同,兩個用于非阻塞接收消息的函數(shù) selectnbrecv 和 selectnbrecv2 其實只是對 chanrecv 返回值的處理稍有不同:

func selectnbrecv(elem unsafe.Pointer, c *hchan) (selected bool) {
    selected, _ = chanrecv(c, elem, false)
    return
}

func selectnbrecv2(elem unsafe.Pointer, received *bool, c *hchan) (selected bool) {
    selected, *received = chanrecv(c, elem, false)
    return
}

因為接收方不需要,所以 selectnbrecv 會直接忽略返回的布爾值,而 selectnbrecv2 會將布爾值回傳給上層;與 chansend 一樣,chanrecv 也提供了一個 block 參數(shù)用于控制這一次接收是否阻塞。

通用情況

在默認(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 的正常 select 語句其實會被展開成如下所示的邏輯,我們可以看到其中處理的三個部分:

selv := [3]scase{}
order := [6]uint16
for i, cas := range cases {
    c := scase{}
    c.kind = ...
    c.elem = ...
    c.c = ...
}
chosen, revcOK := selectgo(selv, order, 3)
if chosen == 0 {
    // ...
    break
}
if chosen == 1 {
    // ...
    break
}
if chosen == 2 {
    // ...
    break
}

展開后的 select 其實包含三部分,最開始初始化數(shù)組并轉(zhuǎn)換 scase 結(jié)構(gòu)體,使用 selectgo 選擇執(zhí)行的 case 以及最后通過 if 判斷選中的情況并執(zhí)行 case 中的表達(dá)式,需要注意的是這里其實也僅僅展開了 select 控制結(jié)構(gòu),select 語句執(zhí)行最重要的過程其實也是選擇 case 執(zhí)行的過程,這是我們在下一節(jié)運行時重點介紹的。

運行時

我們已經(jīng)充分地了解了 select 在編譯期間的處理過程,接下來可以展開介紹 selectgo 函數(shù)的實現(xiàn)原理了。

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

初始化

selectgo 函數(shù)首先會進(jìn)行執(zhí)行必要的一些初始化操作,也就是決定處理 case 的兩個順序,其中一個是 pollOrder 另一個是 lockOrder

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)

    // ...
}

Channel 的輪詢順序是通過 fastrandn 隨機生成的,這其實就導(dǎo)致了如果多個 Channel 同時『響應(yīng)』,select 會隨機選擇其中的一個執(zhí)行;而另一個 lockOrder 就是根據(jù) Channel 的地址確定的,根據(jù)相同的順序鎖定 Channel 能夠避免死鎖的發(fā)生,最后調(diào)用的 sellock 就會按照之前生成的順序鎖定所有的 Channel。

循環(huán)

當(dāng)我們?yōu)?nbsp;select 語句確定了輪詢和鎖定的順序并鎖定了所有的 Channel 之后就會開始進(jìn)入 select 的主循環(huán),查找或者等待 Channel 準(zhǔn)備就緒,循環(huán)中會遍歷所有的 case 并找到需要被喚起的 sudog 結(jié)構(gòu)體,在這段循環(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 — 當(dāng)前 case 表示默認(rèn)情況,如果循環(huán)執(zhí)行到了這種情況就表示前面的所有 case 都沒有被執(zhí)行,所以這里會直接解鎖所有的 Channel 并退出 selectgo 函數(shù),這時也就意味著當(dāng)前 select 結(jié)構(gòu)中的其他收發(fā)語句都是非阻塞的。

這其實是循環(huán)執(zhí)行的第一次遍歷,主要作用就是尋找所有 case 中 Channel 是否有可以立刻被處理的情況,無論是在包含等待的 Goroutine 還是緩沖區(qū)中存在數(shù)據(jù),只要滿足條件就會立刻處理,如果不能立刻找到活躍的 Channel 就會進(jìn)入循環(huán)的下一個過程,按照需要將當(dāng)前的 Goroutine 加入到所有 Channel 的 sendq 或者 recvq 隊列中:

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)

    // ...
}

這里創(chuàng)建 sudog 并入隊的過程其實和 Channel 中直接進(jìn)行發(fā)送和接收時的過程幾乎完全相同,只是除了在入隊之外,這些 sudog 結(jié)構(gòu)體都會被串成鏈表附著在當(dāng)前 Goroutine 上,在入隊之后會調(diào)用 gopark 函數(shù)掛起當(dāng)前的 Goroutine 等待調(diào)度器的喚醒。

等到 select 對應(yīng)的一些 Channel 準(zhǔn)備好之后,當(dāng)前 Goroutine 就會被調(diào)度器喚醒,這時就會繼續(xù)執(zhí)行 selectgo 函數(shù)中剩下的邏輯,也就是從上面 入隊的 sudog 結(jié)構(gòu)體中獲取數(shù)據(jù):

func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool) {
    // ...
    gp.selectDone = 0
    sg = (*sudog)(gp.param)
    gp.param = nil

    casi = -1
    cas = nil
    sglist = gp.waiting
    gp.waiting = nil

    for _, casei := range lockorder {
        k = &scases[casei]
        if sg == sglist {
            casi = int(casei)
            cas = k
        } else {
            if k.kind == caseSend {
                c.sendq.dequeueSudoG(sglist)
            } else {
                c.recvq.dequeueSudoG(sglist)
            }
        }
        sgnext = sglist.waitlink
        sglist.waitlink = nil
        releaseSudog(sglist)
        sglist = sgnext
    }

    c = cas.c

    if cas.kind == caseRecv {
        recvOK = true
    }

    selunlock(scases, lockorder)
    goto retc
    // ...
}

在第三次根據(jù) lockOrder 遍歷全部 case 的過程中,我們會先獲取 Goroutine 接收到的參數(shù) param,這個參數(shù)其實就是被喚醒的 sudog 結(jié)構(gòu),我們會依次對比所有 case 對應(yīng)的 sudog 結(jié)構(gòu)找到被喚醒的 case 并釋放其他未被使用的 sudog 結(jié)構(gòu)。

由于當(dāng)前的 select 結(jié)構(gòu)已經(jīng)挑選了其中的一個 case 進(jìn)行執(zhí)行,那么剩下 case 中沒有被用到的 sudog 其實就會直接忽略并且釋放掉了,為了不影響 Channel 的正常使用,我們還是需要將這些廢棄的 sudog 從 Channel 中出隊;而除此之外的發(fā)生事件導(dǎo)致我們被喚醒的 sudog 結(jié)構(gòu)已經(jīng)在 Channel 進(jìn)行收發(fā)時就已經(jīng)出隊了,不需要我們再次處理,出隊的代碼以及相關(guān)分析其實都在 Channel 一節(jié)中發(fā)送和接收的章節(jié)。

當(dāng)我們在循環(huán)中發(fā)現(xiàn)緩沖區(qū)中有元素或者緩沖區(qū)未滿時就會通過 goto 關(guān)鍵字跳轉(zhuǎn)到以下的兩個代碼段,這兩段代碼的執(zhí)行過程其實都非常簡單,都只是向 Channel 中發(fā)送或者從緩沖區(qū)中直接獲取新的數(shù)據(jù):

bufrecv:
    recvOK = true
    qp = chanbuf(c, c.recvx)
    if cas.elem != nil {
        typedmemmove(c.elemtype, cas.elem, qp)
    }
    typedmemclr(c.elemtype, qp)
    c.recvx++
    if c.recvx == c.dataqsiz {
        c.recvx = 0
    }
    c.qcount--
    selunlock(scases, lockorder)
    goto retc

bufsend:
    typedmemmove(c.elemtype, chanbuf(c, c.sendx), cas.elem)
    c.sendx++
    if c.sendx == c.dataqsiz {
        c.sendx = 0
    }
    c.qcount++
    selunlock(scases, lockorder)
    goto retc

這里在緩沖區(qū)中進(jìn)行的操作和直接對 Channel 調(diào)用 chansend 和 chanrecv 進(jìn)行收發(fā)的過程差不多,執(zhí)行結(jié)束之后就會直接跳到 retc 字段。

兩個直接收發(fā)的情況,其實也就是調(diào)用 Channel 運行時的兩個方法 send 和 recv,這兩個方法會直接操作對應(yīng)的 Channel:

recv:
    recv(c, sg, cas.elem, func() { selunlock(scases, lockorder) }, 2)
    recvOK = true
    goto retc

send:
    send(c, sg, cas.elem, func() { selunlock(scases, lockorder) }, 2)
    goto retc

不過當(dāng)發(fā)送或者接收時,情況就稍微有一點復(fù)雜了,從一個關(guān)閉 Channel 中接收數(shù)據(jù)會直接清除 Channel 中的相關(guān)內(nèi)容,而向一個關(guān)閉的 Channel 發(fā)送數(shù)據(jù)就會直接 panic 造成程序崩潰:

rclose:
    selunlock(scases, lockorder)
    recvOK = false
    if cas.elem != nil {
        typedmemclr(c.elemtype, cas.elem)
    }
    goto retc

sclose:
    selunlock(scases, lockorder)
    panic(plainError("send on closed channel"))

總體來看,Channel 相關(guān)的收發(fā)操作和上一節(jié) Channel 實現(xiàn)原理中介紹的沒有太多出入,只是由于 select 多出了 default 關(guān)鍵字所以會出現(xiàn)非阻塞收發(fā)的情況。

總結(jié)

到這一節(jié)的最后我們需要總結(jié)一下,select 結(jié)構(gòu)的執(zhí)行過程與實現(xiàn)原理,首先在編譯期間,Go 語言會對 select 語句進(jìn)行優(yōu)化,以下是根據(jù) select 中語句的不同選擇了不同的優(yōu)化路徑:

空的 select 語句會被直接轉(zhuǎn)換成 block 函數(shù)的調(diào)用,直接掛起當(dāng)前 Goroutine;

如果 select 語句中只包含一個 case,就會被轉(zhuǎn)換成 if ch == nil { block }; n; 表達(dá)式;

  • 首先判斷操作的 Channel 是不是空的;
  • 然后執(zhí)行 case 結(jié)構(gòu)中的內(nèi)容;

如果 select 語句中只包含兩個 case 并且其中一個是 default,那么 Channel 和接收和發(fā)送操作都會使用 selectnbrecv 和 selectnbsend 非阻塞地執(zhí)行接收和發(fā)送操作;

在默認(rèn)情況下會通過 selectgo 函數(shù)選擇需要執(zhí)行的 case 并通過多個 if 語句執(zhí)行 case 中的表達(dá)式;

在編譯器已經(jīng)對 select 語句進(jìn)行優(yōu)化之后,Go 語言會在運行時執(zhí)行編譯期間展開的 selectgo 函數(shù),這個函數(shù)會按照以下的過程執(zhí)行:

1.隨機生成一個遍歷的輪詢順序 pollOrder 并根據(jù) Channel 地址生成一個用于遍歷的鎖定順序 lockOrder;

2.根據(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)度;

3.當(dāng)調(diào)度器喚醒當(dāng)前 Goroutine 時就會再次按照 lockOrder 遍歷所有的 case,從中查找需要被處理的 sudog 結(jié)構(gòu)并返回對應(yīng)的索引;

然而并不是所有的 select 控制結(jié)構(gòu)都會走到 selectgo 上,很多情況都會被直接優(yōu)化掉,沒有機會調(diào)用 selectgo 函數(shù)。

Go 語言中的 select 關(guān)鍵字與 IO 多路復(fù)用中的 select、epoll 等函數(shù)非常相似,不但 Channel 的收發(fā)操作與等待 IO 的讀寫能找到這種一一對應(yīng)的關(guān)系,這兩者的作用也非常相似;總的來說,select 關(guān)鍵字的實現(xiàn)原理稍顯復(fù)雜,與 Channel 的關(guān)系非常緊密,這里省略了很多 Channel 操作的細(xì)節(jié),數(shù)據(jù)結(jié)構(gòu)一章其實就介紹了 Channel 收發(fā)的相關(guān)細(xì)節(jié)。

到此這篇關(guān)于一文帶你了解Golang中select的實現(xiàn)原理的文章就介紹到這了,更多相關(guān)Golang select內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Go語言Elasticsearch數(shù)據(jù)清理工具思路詳解

    Go語言Elasticsearch數(shù)據(jù)清理工具思路詳解

    這篇文章主要介紹了Go語言Elasticsearch數(shù)據(jù)清理工具思路詳解,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-10-10
  • 夯實Golang基礎(chǔ)之?dāng)?shù)據(jù)類型梳理匯總

    夯實Golang基礎(chǔ)之?dāng)?shù)據(jù)類型梳理匯總

    這篇文章主要8為大家介紹了夯實Golang基礎(chǔ)之?dāng)?shù)據(jù)類型梳理匯總,有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2023-10-10
  • 淺談beego默認(rèn)處理靜態(tài)文件性能低下的問題

    淺談beego默認(rèn)處理靜態(tài)文件性能低下的問題

    下面小編就為大家?guī)硪黄獪\談beego默認(rèn)處理靜態(tài)文件性能低下的問題。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-06-06
  • Go處理PDF的實現(xiàn)代碼

    Go處理PDF的實現(xiàn)代碼

    這篇文章主要介紹了Go處理PDF的實現(xiàn)代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-01-01
  • Go+Kafka實現(xiàn)延遲消息的實現(xiàn)示例

    Go+Kafka實現(xiàn)延遲消息的實現(xiàn)示例

    本文主要介紹了Go+Kafka實現(xiàn)延遲消息的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07
  • Go方法簡單性和高效性的充分體現(xiàn)詳解

    Go方法簡單性和高效性的充分體現(xiàn)詳解

    本文深入探討了Go語言中方法的各個方面,包括基礎(chǔ)概念、定義與聲明、特性、實戰(zhàn)應(yīng)用以及性能考量,文章充滿技術(shù)深度,通過實例和代碼演示,力圖幫助讀者全面理解Go方法的設(shè)計哲學(xué)和最佳實踐
    2023-10-10
  • Go Module依賴管理的實現(xiàn)

    Go Module依賴管理的實現(xiàn)

    Go Module是Go語言的官方依賴管理解決方案,其提供了一種簡單、可靠的方式來管理項目的依賴關(guān)系,本文主要介紹了Go Module依賴管理的實現(xiàn),感興趣的可以了解一下
    2024-06-06
  • Golang語言中的Prometheus的日志模塊使用案例代碼編寫

    Golang語言中的Prometheus的日志模塊使用案例代碼編寫

    這篇文章主要介紹了Golang語言中的Prometheus的日志模塊使用案例,本文給大家分享源代碼編寫方法,感興趣的朋友跟隨小編一起看看吧
    2024-08-08
  • go語言開發(fā)環(huán)境安裝及第一個go程序(推薦)

    go語言開發(fā)環(huán)境安裝及第一個go程序(推薦)

    這篇文章主要介紹了go語言開發(fā)環(huán)境安裝及第一個go程序,這篇通過實例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-02-02
  • Go使用Protocol?Buffers在數(shù)據(jù)序列化的優(yōu)勢示例詳解

    Go使用Protocol?Buffers在數(shù)據(jù)序列化的優(yōu)勢示例詳解

    這篇文章主要為大家介紹了Go使用Protocol?Buffers在數(shù)據(jù)序列化的優(yōu)勢示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-11-11

最新評論