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

詳解Golang中select的使用與源碼分析

 更新時(shí)間:2022年12月31日 09:45:32   作者:胡大海  
select?是?Go?提供的?IO?多路復(fù)用機(jī)制,可以用多個(gè)?case?同時(shí)監(jiān)聽(tīng)多個(gè)?channl?的讀寫(xiě)狀態(tài)。本文將從源碼角度帶大家了解一下select的使用,需要的可以參考一下

背景

golang 中主推 channel 通信。單個(gè) channel 的通信可以通過(guò)一個(gè)goroutinechannel 發(fā)數(shù)據(jù),另外一個(gè)從channel取數(shù)據(jù)進(jìn)行。這是阻塞的,因?yàn)橐腠樌麍?zhí)行完這個(gè)步驟,需要 channel 準(zhǔn)備好才行,準(zhǔn)備好的條件如下:

1.發(fā)送

  • 緩存有空間(如果是有緩存的 channel)
  • 有等待接收的 goroutine

2.接收

  • 緩存有數(shù)據(jù)(如果是有緩存的 channel)
  • 有等待發(fā)送的 goroutine

對(duì)channel實(shí)際使用中還有如下兩個(gè)需求,這個(gè)時(shí)候就需要select了。

  • 同時(shí)監(jiān)聽(tīng)多個(gè)channel
  • 在沒(méi)有channel準(zhǔn)備好的時(shí)候,也可以往下執(zhí)行。

select 流程

1.空select。作用是阻塞當(dāng)前goroutine。不要用for{}來(lái)阻塞goroutine,因?yàn)闀?huì)占用cpu。而select{}不會(huì),因?yàn)楫?dāng)前goroutine不會(huì)再被調(diào)度。

 if len(cases) == 0 {
         block()
 }

2.配置好poll的順序。由于是同時(shí)監(jiān)聽(tīng)多個(gè)channel的發(fā)送或者接收,所以需要按照一定的順序查看哪個(gè)channel準(zhǔn)備好了。如果每次采用select中的順序查看channel是否準(zhǔn)備好了,那么只要在前面的channel準(zhǔn)備好的足夠快,那么會(huì)造成后面的channel即使準(zhǔn)備好了,也永遠(yuǎn)不會(huì)被執(zhí)行。打亂順序的邏輯如下,采用了洗牌算法\color{red}{洗牌算法}洗牌算法,注意此過(guò)程中會(huì)過(guò)濾掉channel為nil的case。\color{red}{注意此過(guò)程中會(huì)過(guò)濾掉 channel 為 nil 的 case。}注意此過(guò)程中會(huì)過(guò)濾掉channel為nil的case。

 // generate permuted order
 norder := 0
 for i := range scases {
         cas := &scases[i]

         // Omit cases without channels from the poll and lock orders.
         if cas.c == nil {
                 cas.elem = nil // allow GC
                 continue
         }

         j := fastrandn(uint32(norder + 1))
         pollorder[norder] = pollorder[j]
         pollorder[j] = uint16(i)
         norder++
 }

3.配置好lock的順序。由于可能會(huì)修改channel中的數(shù)據(jù),所以在打算往channel中發(fā)送數(shù)據(jù)或者從channel接收數(shù)據(jù)的時(shí)候,需要鎖住 channel。而一個(gè)channel可能被多個(gè)select監(jiān)聽(tīng),如果兩個(gè)select對(duì)兩個(gè)channel A和B,分別按照順序A, B和B,A上鎖,是可能會(huì)造成死鎖的,導(dǎo)致兩個(gè)select都執(zhí)行不下去。

所以select中鎖住channel的順序至關(guān)重要,解決方案是按照channel的地址的順序鎖住channel。因?yàn)樵趦蓚€(gè)selectchannel有交集的時(shí)候,都是按照交集中channel的地址順序鎖channel。

實(shí)際排序代碼如下,采用堆排序算法\color{red}{堆排序算法}堆排序算法按照channel的地址從小到大對(duì)channel進(jìn)行排序。

 // sort the cases by Hchan address to get the locking order.
 // simple heap sort, to guarantee n log n time and constant stack footprint.
 for i := range lockorder {
         j := i
         // Start with the pollorder to permute cases on the same channel.
         c := scases[pollorder[i]].c
         for j > 0 && scases[lockorder[(j-1)/2]].c.sortkey() < c.sortkey() {
                 k := (j - 1) / 2
                 lockorder[j] = lockorder[k]
                 j = k
         }
         lockorder[j] = pollorder[i]
 }
 for i := len(lockorder) - 1; i >= 0; i-- {
         o := lockorder[i]
         c := scases[o].c
         lockorder[i] = lockorder[0]
         j := 0
         for {
                 k := j*2 + 1
                 if k >= i {
                         break
                 }
                 if k+1 < i && scases[lockorder[k]].c.sortkey() < scases[lockorder[k+1]].c.sortkey() {
                         k++
                 }
                 if c.sortkey() < scases[lockorder[k]].c.sortkey() {
                         lockorder[j] = lockorder[k]
                         j = k
                         continue
                 }
                 break
         }
         lockorder[j] = o
 }

4.鎖住select中的所有channel。要查看channel中的數(shù)據(jù)了。

 // lock all the channels involved in the select
 sellock(scases, lockorder)

5.第一輪查看是否已有準(zhǔn)備好的channel。如果有直接發(fā)送數(shù)據(jù)到channel或者從channel接收數(shù)據(jù)。注意selectchannel切片中,前面部分是從channel接收數(shù)據(jù)的case,后半部分是往channel發(fā)送數(shù)據(jù)的case。

按照pollorder順序查看是否有channel準(zhǔn)備好了。

 for _, casei := range pollorder {
         casi = int(casei)
         cas = &scases[casi]
         c = cas.c
         if casi >= nsends {
                 sg = c.sendq.dequeue()
                 if sg != nil {
                         goto recv
                 }
                 if c.qcount > 0 {
                         goto bufrecv
                 }
                 if c.closed != 0 {
                         goto rclose
                 }
         } else {
                 if raceenabled {
                         racereadpc(c.raceaddr(), casePC(casi), chansendpc)
                 }
                 if c.closed != 0 {
                         goto sclose
                 }
                 sg = c.recvq.dequeue()
                 if sg != nil {
                         goto send
                 }
                 if c.qcount < c.dataqsiz {
                         goto bufsend
                 }
         }
 }

6.直接執(zhí)行default分支

 if !block {
         selunlock(scases, lockorder)
         casi = -1
         goto retc
 }

7.第二輪遍歷channel。創(chuàng)建sudog把當(dāng)前goroutine放到每個(gè)channel的等待列表中去,等待channel準(zhǔn)備好時(shí)被喚醒。

 // pass 2 - enqueue on all chans
 gp = getg()
 if gp.waiting != nil {
         throw("gp.waiting != nil")
 }
 nextp = &gp.waiting
 for _, casei := range lockorder {
         casi = int(casei)
         cas = &scases[casi]
         c = cas.c
         sg := acquireSudog()
         sg.g = gp
         sg.isSelect = true
         // No stack splits between assigning elem and enqueuing
         // sg on gp.waiting where copystack can find it.
         sg.elem = cas.elem
         sg.releasetime = 0
         if t0 != 0 {
                 sg.releasetime = -1
         }
         sg.c = c
         // Construct waiting list in lock order.
         *nextp = sg
         nextp = &sg.waitlink

         if casi < nsends {
                 c.sendq.enqueue(sg)
         } else {
                 c.recvq.enqueue(sg)
         }
 }

8.等待被喚醒。其中gopark的時(shí)候會(huì)釋放對(duì)所有channel占用的鎖。

 // wait for someone to wake us up
 gp.param = nil
 // Signal to anyone trying to shrink our stack that we're about
 // to park on a channel. The window between when this G's status
 // changes and when we set gp.activeStackChans is not safe for
 // stack shrinking.
 atomic.Store8(&gp.parkingOnChan, 1)
 gopark(selparkcommit, nil, waitReasonSelect, traceEvGoBlockSelect, 1)
 gp.activeStackChans = false

9.被喚醒

  • 鎖住所有channel
  • 清理當(dāng)前goroutine的等待sudog
  • 找到是被哪個(gè)channel喚醒的,并清理每個(gè)channel上當(dāng)前的goroutine對(duì)應(yīng)的sudog
 sellock(scases, lockorder)

 gp.selectDone = 0
 sg = (*sudog)(gp.param)
 gp.param = nil

 // pass 3 - dequeue from unsuccessful chans
 // otherwise they stack up on quiet channels
 // record the successful case, if any.
 // We singly-linked up the SudoGs in lock order.
 casi = -1
 cas = nil
 caseSuccess = false
 sglist = gp.waiting
 // Clear all elem before unlinking from gp.waiting.
 for sg1 := gp.waiting; sg1 != nil; sg1 = sg1.waitlink {
         sg1.isSelect = false
         sg1.elem = nil
         sg1.c = nil
 }
 gp.waiting = nil

 for _, casei := range lockorder {
         k = &scases[casei]
         if sg == sglist {
                 // sg has already been dequeued by the G that woke us up.
                 casi = int(casei)
                 cas = k
                 caseSuccess = sglist.success
                 if sglist.releasetime > 0 {
                         caseReleaseTime = sglist.releasetime
                 }
         } else {
                 c = k.c
                 if int(casei) < nsends {
                         c.sendq.dequeueSudoG(sglist)
                 } else {
                         c.recvq.dequeueSudoG(sglist)
                 }
         }
         sgnext = sglist.waitlink
         sglist.waitlink = nil
         releaseSudog(sglist)
         sglist = sgnext
 }

到此這篇關(guān)于詳解Golang中select的使用與源碼分析的文章就介紹到這了,更多相關(guān)Golang select內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • golang 實(shí)現(xiàn)tcp server端和client端,并計(jì)算RTT時(shí)間操作

    golang 實(shí)現(xiàn)tcp server端和client端,并計(jì)算RTT時(shí)間操作

    這篇文章主要介紹了golang 實(shí)現(xiàn)tcp server端和client端,并計(jì)算RTT時(shí)間操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-12-12
  • 安裝Sublime?Text支持Go插件的方法步驟

    安裝Sublime?Text支持Go插件的方法步驟

    本文主要介紹了安裝Sublime?Text支持Go插件的方法步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • golang操作Redis的實(shí)現(xiàn)示例

    golang操作Redis的實(shí)現(xiàn)示例

    本文主要介紹了golang操作Redis的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2024-04-04
  • golang判斷字符串是是否包含另一字符串的示例代碼

    golang判斷字符串是是否包含另一字符串的示例代碼

    在Go語(yǔ)言中,可以使用strings.Contains()函數(shù)來(lái)判斷一個(gè)字符串是否包含另一個(gè)字符串,該函數(shù)接受兩個(gè)參數(shù):要搜索的字符串和要查找的子字符串,如果子字符串存在于要搜索的字符串中,則返回true,否則返回false,下面通過(guò)示例詳細(xì)介紹,感興趣的朋友一起看看吧
    2023-09-09
  • 一文搞懂Golang中的內(nèi)存逃逸

    一文搞懂Golang中的內(nèi)存逃逸

    內(nèi)存逃逸是 Go 語(yǔ)言中一個(gè)重要的概念,涉及到程序的性能優(yōu)化和內(nèi)存管理,了解內(nèi)存逃逸可以幫助我們編寫(xiě)更高效的代碼,本文將從基本概念入手,深入講解 Go 語(yǔ)言中的內(nèi)存逃逸現(xiàn)象,以及如何避免,需要的朋友可以參考下
    2023-12-12
  • Go語(yǔ)言實(shí)現(xiàn)釘釘發(fā)送通知

    Go語(yǔ)言實(shí)現(xiàn)釘釘發(fā)送通知

    本文通過(guò)代碼給大家介紹了Go語(yǔ)言實(shí)現(xiàn)釘釘發(fā)送通知,代碼簡(jiǎn)單易懂,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-11-11
  • GO中Json解析的幾種方式

    GO中Json解析的幾種方式

    本文主要介紹了GO中Json解析的幾種方式,詳細(xì)的介紹了幾種方法,?文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2024-01-01
  • 如何組織Go代碼目錄結(jié)構(gòu)依賴(lài)注入wire使用解析

    如何組織Go代碼目錄結(jié)構(gòu)依賴(lài)注入wire使用解析

    這篇文章主要為大家介紹了如何組織Go代碼目錄結(jié)構(gòu)依賴(lài)注入wire使用解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-07-07
  • Go語(yǔ)言中 Channel 詳解

    Go語(yǔ)言中 Channel 詳解

    Go 語(yǔ)言中的 channel 是實(shí)現(xiàn) goroutine 間無(wú)鎖通信的關(guān)鍵機(jī)制,他使得寫(xiě)多線程并發(fā)程序變得簡(jiǎn)單、靈活、觸手可得。下面就個(gè)人理解對(duì) channel 使用過(guò)程中應(yīng)該注意的地方進(jìn)行一個(gè)簡(jiǎn)要的總結(jié)。
    2018-10-10
  • Go編寫(xiě)定時(shí)器與定時(shí)任務(wù)詳解(附第三方庫(kù)gocron用法)

    Go編寫(xiě)定時(shí)器與定時(shí)任務(wù)詳解(附第三方庫(kù)gocron用法)

    當(dāng)需要每天執(zhí)行定時(shí)任務(wù)的時(shí)候就需要定時(shí)器來(lái)處理了,周期任務(wù),倒計(jì)時(shí)任務(wù),定點(diǎn)任務(wù)等,下面這篇文章主要給大家介紹了關(guān)于Go編寫(xiě)定時(shí)器與定時(shí)任務(wù)的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-07-07

最新評(píng)論