最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Go底層select的原理分析

 更新時間:2025年06月25日 11:02:35   作者:在成都搬磚的鴨鴨  
這篇文章主要介紹了Go底層select的原理,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教

背景

select多路復(fù)用在go的異步和并發(fā)控制場景中非常好用,對于無case和只有單個case的情況,編譯器在編譯的時候就會對其做優(yōu)化,無case就相當(dāng)于調(diào)用了一個阻塞函數(shù),單個case就相當(dāng)于對一個通道進(jìn)行讀寫操作,如果單個case中有default分支時,就相當(dāng)于是一個if else邏輯,對于多個case的情況,是在運行時調(diào)用selectgo函數(shù)決定的,接下來我們就來研究一下selectgo函數(shù)。

go版本

$ go version
go version go1.21.4 windows/386

selectgo函數(shù)解釋

【1】函數(shù)參數(shù)解釋

selectgo函數(shù)位于:src/runtime/select.go中,定義如下:

//cas0:case數(shù)組地址,按照往通道寫數(shù)據(jù)在前,從通道讀數(shù)據(jù)在后的排列順序(編譯時編譯器優(yōu)化行為操作的)
//nsends:往通道寫數(shù)據(jù)的case數(shù)量
//nrecvs:從通道讀數(shù)據(jù)的case數(shù)量
//block:是否阻塞
//返回值分別代表選中規(guī)定case位置和是否成功從通道接收數(shù)據(jù),如果選中的是default,第一個返回值就返回-1
func selectgo(cas0 *scase, order0 *uint16, pc0 *uintptr, nsends, nrecvs int, block bool) (int, bool)

select中每一個case都對應(yīng)一個scase結(jié)構(gòu),定義如下:

type scase struct {
	c    *hchan         //case對應(yīng)的讀或?qū)懲ǖ?
	elem unsafe.Pointer //指向要寫入元素或存放讀取元素的地址
}

【2】函數(shù)具體解釋

selectgo函數(shù)中會遍歷所有的case,為確保遍歷case的隨機性和安全性,有兩個關(guān)鍵的順序:pollorder和lockorder,不用關(guān)心其具體實現(xiàn),明白其的作用就行。

  • pollorder:隨機的case順序,確保公平的處理每一個case。
  • lockorder:加鎖的case順序,確保并發(fā)安全。

計算出pollorder和lockorder順序之后,會根據(jù)這2個順序進(jìn)行遍歷分為了3步。

第一步:遍歷pollorder,選出準(zhǔn)備好的case

第一部分的代碼如下:

func selectgo(cas0 *scase, order0 *uint16, pc0 *uintptr, nsends, nrecvs int, block bool) (int, bool) {
	...
	
	var casi int   //準(zhǔn)備好的case位置
	var cas *scase //case對象
	var caseSuccess bool
	var caseReleaseTime int64 = -1
	var recvOK bool //如果是從通道讀取數(shù)據(jù),是否讀取成功
	for _, casei := range pollorder { //遍歷隨機順序的case
		casi = int(casei)   //case的位置
		cas = &scases[casi] //case對象
		c = cas.c //case通道

		if casi >= nsends { //前面講過,寫通道在前,讀通道在后,所以這里是讀通道case
			sg = c.sendq.dequeue() //取出往讀通道寫數(shù)據(jù)的協(xié)程隊列中的第一個協(xié)程
			if sg != nil { //如果存在往通道寫數(shù)據(jù)的協(xié)程
				goto recv  //從往通道寫數(shù)據(jù)的協(xié)程中讀取數(shù)據(jù)并返回case位置和讀取結(jié)果
			}
			if c.qcount > 0 { //如果緩沖區(qū)還有數(shù)據(jù)
				goto bufrecv  //從緩沖區(qū)讀取數(shù)據(jù)并返回case位置和讀取結(jié)果
			}
			if c.closed != 0 { //如果通道已關(guān)閉
				goto rclose    //釋放相關(guān)資源
			}
		} else { //寫通道的case
			if raceenabled {
				racereadpc(c.raceaddr(), casePC(casi), chansendpc)
			}
			if c.closed != 0 { //如果通道已經(jīng)關(guān)閉
				goto sclose    //直接panic
			}
			sg = c.recvq.dequeue() //從正在往通道讀數(shù)據(jù)的協(xié)程隊列中取得第一個
			if sg != nil { //如果往通道讀數(shù)據(jù)的協(xié)程存在
				goto send  //發(fā)送數(shù)據(jù)到讀通道的協(xié)程
			}
			if c.qcount < c.dataqsiz { //緩沖區(qū)還有位置
				goto bufsend
			}
		}
	}

	if !block { //如果不阻塞,也就是帶default分支
		selunlock(scases, lockorder)
		casi = -1 //case位置為-1
		goto retc //直接返回,不用進(jìn)入下一步
	}
	
	...
}

bufrecv標(biāo)簽:

	bufrecv:
	recvOK = true  //返回讀數(shù)據(jù)成功
	qp = chanbuf(c, c.recvx) //緩沖區(qū)中要讀取數(shù)據(jù)的地址
	if cas.elem != nil {
		typedmemmove(c.elemtype, cas.elem, qp) //將讀取的緩沖區(qū)數(shù)據(jù)拷貝到case中的elem位置
	}
	typedmemclr(c.elemtype, qp) //清理緩沖區(qū)被讀的數(shù)據(jù)
	c.recvx++ //讀取緩沖區(qū)的位置+1
	if c.recvx == c.dataqsiz { //下一個要讀取緩沖區(qū)的位置如果等于緩沖區(qū)大小就將下次要讀取的緩沖區(qū)位置置為0
		c.recvx = 0
	}
	c.qcount-- //緩沖區(qū)中元素個數(shù)-1
	selunlock(scases, lockorder)
	goto retc

bufsend標(biāo)簽:

	bufsend:
	typedmemmove(c.elemtype, chanbuf(c, c.sendx), cas.elem) //將case中要寫入的元素寫到緩沖區(qū)
	c.sendx++ //寫入緩沖區(qū)的位置+1
	if c.sendx == c.dataqsiz { //如果下次要寫入緩沖區(qū)的位置等于緩沖區(qū)的大小就將緩沖區(qū)寫入位置置為開頭
		c.sendx = 0
	}
	c.qcount++ //緩沖區(qū)元素數(shù)量+1
	selunlock(scases, lockorder)
	goto retc	

recv標(biāo)簽:

recv:
	recv(c, sg, cas.elem, func() { selunlock(scases, lockorder) }, 2) //從寫通道的協(xié)程讀取數(shù)據(jù)
	if debugSelect {
		print("syncrecv: cas0=", cas0, " c=", c, "\n")
	}
	recvOK = true //返回成功讀取
	goto retc

rclose標(biāo)簽:

rclose:
	selunlock(scases, lockorder)
	recvOK = false //從通道中讀取數(shù)據(jù)失敗
	if cas.elem != nil {
		typedmemclr(c.elemtype, cas.elem) //釋放case中元素的空間
	}
	if raceenabled {
		raceacquire(c.raceaddr())
	}
	goto retc

send標(biāo)簽:

send:
	send(c, sg, cas.elem, func() { selunlock(scases, lockorder) }, 2) //發(fā)送數(shù)據(jù)到往通道讀數(shù)據(jù)的協(xié)程
	if debugSelect {
		print("syncsend: cas0=", cas0, " c=", c, "\n")
	}
	goto retc

retc標(biāo)簽:

retc:
	if caseReleaseTime > 0 {
		blockevent(caseReleaseTime-t0, 1)
	}
	return casi, recvOK  //返回case位置和是否從通道成功讀取數(shù)據(jù)

sclose標(biāo)簽:

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

上面就是selectgo函數(shù)第一部分的邏輯,第一部分就是遍歷一個隨機的case順序,如果有符合條件的case就返回case的位置并且返回讀數(shù)據(jù)的結(jié)果,如果沒有case符合條件但是有default分支就返回-1,如果沒default分支就進(jìn)入下一步。

第二步:將當(dāng)前goroutine放到所有case通道中對應(yīng)的收發(fā)隊列上

第二部分的代碼如下:

func selectgo(cas0 *scase, order0 *uint16, pc0 *uintptr, nsends, nrecvs int, block bool) (int, bool) {
	...

	gp = getg() //獲取當(dāng)前協(xié)程
	if gp.waiting != nil {
		throw("gp.waiting != nil")
	}
	nextp = &gp.waiting
	for _, casei := range lockorder { //按照對case加鎖的順序遍歷case
		casi = int(casei)   //case的位置
		cas = &scases[casi] //case對象
		c = cas.c  //case對象中的通道
		sg := acquireSudog() //初始化一個協(xié)程等待結(jié)構(gòu)
		sg.g = gp //協(xié)程等待結(jié)構(gòu)綁定協(xié)程
		sg.isSelect = true //表示該協(xié)程等待結(jié)構(gòu)與select操作相關(guān)
		sg.elem = cas.elem 
		sg.releasetime = 0
		if t0 != 0 {
			sg.releasetime = -1
		}
		sg.c = c
		*nextp = sg
		nextp = &sg.waitlink

		if casi < nsends { //如果case上是往通道寫數(shù)據(jù),就將綁定當(dāng)前協(xié)程的等待對象插入當(dāng)前case通道的發(fā)送隊列中
			c.sendq.enqueue(sg) 
		} else { //如果case上是往通道讀數(shù)據(jù),就將綁定當(dāng)前協(xié)程的等待對象插入當(dāng)前case通道的接收隊列中
			c.recvq.enqueue(sg)
		}
	}

	...
}

第二部分就是將當(dāng)前協(xié)程放到每個case中的通道對應(yīng)的收發(fā)隊列中去。

第三步:喚醒groutine

第三部分代碼如下:

func selectgo(cas0 *scase, order0 *uint16, pc0 *uintptr, nsends, nrecvs int, block bool) (int, bool) {
	...

	sg = (*sudog)(gp.param) //被喚醒的協(xié)程等待結(jié)構(gòu)
	gp.param = nil
	
	casi = -1  //case位置
	cas = nil  //case對象
	caseSuccess = false
	sglist = gp.waiting //lockorder順序的協(xié)程等待結(jié)構(gòu)隊列,這里是隊列中的第一個協(xié)程等待結(jié)構(gòu)
	for sg1 := gp.waiting; sg1 != nil; sg1 = sg1.waitlink { //清空協(xié)程等待結(jié)構(gòu)隊列中元素便于進(jìn)行垃圾回收
		sg1.isSelect = false
		sg1.elem = nil
		sg1.c = nil
	}
	gp.waiting = nil

	for _, casei := range lockorder { //根據(jù)對case的加鎖順序進(jìn)行遍歷
		k = &scases[casei] //當(dāng)前case
		if sg == sglist {  //喚醒的協(xié)程等待結(jié)構(gòu)是當(dāng)前case的
			casi = int(casei) //喚醒的case位置
			cas = k //喚醒的case對象
			caseSuccess = sglist.success //往通道讀取或?qū)憯?shù)據(jù)結(jié)果
			if sglist.releasetime > 0 {
				caseReleaseTime = sglist.releasetime
			}
		} else { //喚醒的協(xié)程等待結(jié)構(gòu)不是當(dāng)前case的
			c = k.c
			if int(casei) < nsends { //case為發(fā)送通道,就是釋放當(dāng)前case通道里sendq隊列的協(xié)程等待結(jié)構(gòu)對象
				c.sendq.dequeueSudoG(sglist)
			} else {  //case為讀取通道,就是釋放當(dāng)前case通道里recvq隊列的協(xié)程等待結(jié)構(gòu)對象
				c.recvq.dequeueSudoG(sglist)
			}
		}
		sgnext = sglist.waitlink //下一個協(xié)程等待結(jié)構(gòu)
		sglist.waitlink = nil
		releaseSudog(sglist) //釋放上一個協(xié)程等待結(jié)構(gòu)
		sglist = sgnext
	}

	...
}

第三部分就是某一個case上的協(xié)程等待結(jié)構(gòu)被喚醒時,會先執(zhí)行通道上對應(yīng)的收發(fā)操作, 然后去將所有case上的協(xié)程等待結(jié)構(gòu)釋放掉。

總結(jié)

select雖然使用起來簡單,但其實現(xiàn)邏輯還是比較復(fù)雜的,通過熟悉其實現(xiàn),我們能理解對多個通道進(jìn)行操作時候,可以為每一個通道創(chuàng)建一個協(xié)程去操作,這無疑增加了GC開銷,但是使用select采用了多路復(fù)用的思想,將一個協(xié)程綁定在多個協(xié)程等待對象上,而且對case使用了隨機順序,確保每一個case都能公平的被執(zhí)行。

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Golang使用gorm實現(xiàn)分頁功能的示例代碼

    Golang使用gorm實現(xiàn)分頁功能的示例代碼

    在提供列表接口時一般要用到分頁,對于存儲在某些數(shù)據(jù)庫中的數(shù)據(jù)進(jìn)行分頁起來非常的方便,下文給出一個通過gorm進(jìn)行分頁并通過http返回數(shù)據(jù)的例子,感興趣的小伙幫跟著小編一起來看看吧
    2024-10-10
  • Go?1.21新內(nèi)置函數(shù)min、max和clear的用法詳解

    Go?1.21新內(nèi)置函數(shù)min、max和clear的用法詳解

    Go?1.21?版本已經(jīng)正式發(fā)布,它帶來了許多新特性和改進(jìn),其中引入了的三個新內(nèi)置函數(shù):max、min?和?clear,接下來我們就來看看這些函數(shù)的用途和特點吧
    2023-08-08
  • 利用Golang實現(xiàn)對配置文件加密

    利用Golang實現(xiàn)對配置文件加密

    在實際的應(yīng)用中,配置文件通常包含了一些敏感的信息,如數(shù)據(jù)庫密碼、API密鑰等,為了保護這些敏感信息不被惡意獲取,我們可以對配置文件進(jìn)行加密,本文將介紹如何使用Go語言實現(xiàn)對配置文件的加密,需要的朋友可以參考下
    2023-10-10
  • Go定時器的三種實現(xiàn)方式示例詳解

    Go定時器的三種實現(xiàn)方式示例詳解

    這篇文章主要為大家介紹了Go定時器的三種實現(xiàn)方式示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12
  • Go語言HttpRouter路由使用方法詳解

    Go語言HttpRouter路由使用方法詳解

    這篇文章主要介紹了Go語言HttpRouter路由使用方法詳解,需要的朋友可以參考下
    2022-04-04
  • Go 微服務(wù)開發(fā)框架DMicro設(shè)計思路詳解

    Go 微服務(wù)開發(fā)框架DMicro設(shè)計思路詳解

    這篇文章主要為大家介紹了Go 微服務(wù)開發(fā)框架DMicro設(shè)計思路詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-10-10
  • 使用Go語言創(chuàng)建WebSocket服務(wù)的實現(xiàn)示例

    使用Go語言創(chuàng)建WebSocket服務(wù)的實現(xiàn)示例

    這篇文章主要介紹了使用Go語言創(chuàng)建WebSocket服務(wù)的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-03-03
  • Go語言基礎(chǔ)之網(wǎng)絡(luò)編程全面教程示例

    Go語言基礎(chǔ)之網(wǎng)絡(luò)編程全面教程示例

    這篇文章主要為大家介紹了Go語言基礎(chǔ)之網(wǎng)絡(luò)編程全面教程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-12-12
  • go設(shè)置多個GOPATH的方式

    go設(shè)置多個GOPATH的方式

    這篇文章主要介紹了go設(shè)置多個GOPATH的方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-05-05
  • 教你一招完美解決vscode安裝go插件失敗問題

    教你一招完美解決vscode安裝go插件失敗問題

    VSCode是我們開發(fā)go程序的常用工具,但是安裝VSCode成功后,創(chuàng)建一個.go文件居然提示錯誤了,所以下面下面這篇文章主要給大家介紹了如何通過一招完美解決vscode安裝go插件失敗問題的相關(guān)資料,需要的朋友可以參考下
    2022-07-07

最新評論

永宁县| 青州市| 颍上县| 碌曲县| 基隆市| 竹北市| 鄂温| 云和县| 大足县| 阜阳市| 且末县| 康马县| 库车县| 鄂托克旗| 根河市| 化隆| 广灵县| 东城区| 甘孜县| 东阿县| 建始县| 姜堰市| 成安县| 株洲市| 邛崃市| 启东市| 墨脱县| 内乡县| 庆阳市| 博罗县| 克什克腾旗| 刚察县| 乐清市| 宣武区| 道孚县| 高碑店市| 凤山市| 土默特右旗| 科技| 谷城县| 洛扎县|