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

Go底層select的原理分析

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

背景

select多路復(fù)用在go的異步和并發(fā)控制場景中非常好用,對(duì)于無case和只有單個(gè)case的情況,編譯器在編譯的時(shí)候就會(huì)對(duì)其做優(yōu)化,無case就相當(dāng)于調(diào)用了一個(gè)阻塞函數(shù),單個(gè)case就相當(dāng)于對(duì)一個(gè)通道進(jìn)行讀寫操作,如果單個(gè)case中有default分支時(shí),就相當(dāng)于是一個(gè)if else邏輯,對(duì)于多個(gè)case的情況,是在運(yùn)行時(shí)調(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ù)在后的排列順序(編譯時(shí)編譯器優(yōu)化行為操作的)
//nsends:往通道寫數(shù)據(jù)的case數(shù)量
//nrecvs:從通道讀數(shù)據(jù)的case數(shù)量
//block:是否阻塞
//返回值分別代表選中規(guī)定case位置和是否成功從通道接收數(shù)據(jù),如果選中的是default,第一個(gè)返回值就返回-1
func selectgo(cas0 *scase, order0 *uint16, pc0 *uintptr, nsends, nrecvs int, block bool) (int, bool)

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

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

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

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

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

計(jì)算出pollorder和lockorder順序之后,會(huì)根據(jù)這2個(gè)順序進(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對(duì)象
	var caseSuccess bool
	var caseReleaseTime int64 = -1
	var recvOK bool //如果是從通道讀取數(shù)據(jù),是否讀取成功
	for _, casei := range pollorder { //遍歷隨機(jī)順序的case
		casi = int(casei)   //case的位置
		cas = &scases[casi] //case對(duì)象
		c = cas.c //case通道

		if casi >= nsends { //前面講過,寫通道在前,讀通道在后,所以這里是讀通道case
			sg = c.sendq.dequeue() //取出往讀通道寫數(shù)據(jù)的協(xié)程隊(duì)列中的第一個(gè)協(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é)程隊(duì)列中取得第一個(gè)
			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 { //下一個(gè)要讀取緩沖區(qū)的位置如果等于緩沖區(qū)大小就將下次要讀取的緩沖區(qū)位置置為0
		c.recvx = 0
	}
	c.qcount-- //緩沖區(qū)中元素個(gè)數(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ū)元素?cái)?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ù)第一部分的邏輯,第一部分就是遍歷一個(gè)隨機(jī)的case順序,如果有符合條件的case就返回case的位置并且返回讀數(shù)據(jù)的結(jié)果,如果沒有case符合條件但是有default分支就返回-1,如果沒default分支就進(jìn)入下一步。

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

第二部分的代碼如下:

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 { //按照對(duì)case加鎖的順序遍歷case
		casi = int(casei)   //case的位置
		cas = &scases[casi] //case對(duì)象
		c = cas.c  //case對(duì)象中的通道
		sg := acquireSudog() //初始化一個(gè)協(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é)程的等待對(duì)象插入當(dāng)前case通道的發(fā)送隊(duì)列中
			c.sendq.enqueue(sg) 
		} else { //如果case上是往通道讀數(shù)據(jù),就將綁定當(dāng)前協(xié)程的等待對(duì)象插入當(dāng)前case通道的接收隊(duì)列中
			c.recvq.enqueue(sg)
		}
	}

	...
}

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

第三步:喚醒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對(duì)象
	caseSuccess = false
	sglist = gp.waiting //lockorder順序的協(xié)程等待結(jié)構(gòu)隊(duì)列,這里是隊(duì)列中的第一個(gè)協(xié)程等待結(jié)構(gòu)
	for sg1 := gp.waiting; sg1 != nil; sg1 = sg1.waitlink { //清空協(xié)程等待結(jié)構(gòu)隊(duì)列中元素便于進(jìn)行垃圾回收
		sg1.isSelect = false
		sg1.elem = nil
		sg1.c = nil
	}
	gp.waiting = nil

	for _, casei := range lockorder { //根據(jù)對(duì)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對(duì)象
			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隊(duì)列的協(xié)程等待結(jié)構(gòu)對(duì)象
				c.sendq.dequeueSudoG(sglist)
			} else {  //case為讀取通道,就是釋放當(dāng)前case通道里recvq隊(duì)列的協(xié)程等待結(jié)構(gòu)對(duì)象
				c.recvq.dequeueSudoG(sglist)
			}
		}
		sgnext = sglist.waitlink //下一個(gè)協(xié)程等待結(jié)構(gòu)
		sglist.waitlink = nil
		releaseSudog(sglist) //釋放上一個(gè)協(xié)程等待結(jié)構(gòu)
		sglist = sgnext
	}

	...
}

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

總結(jié)

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

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

相關(guān)文章

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

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

    在提供列表接口時(shí)一般要用到分頁,對(duì)于存儲(chǔ)在某些數(shù)據(jù)庫中的數(shù)據(jù)進(jìn)行分頁起來非常的方便,下文給出一個(gè)通過gorm進(jìn)行分頁并通過http返回?cái)?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),其中引入了的三個(gè)新內(nèi)置函數(shù):max、min?和?clear,接下來我們就來看看這些函數(shù)的用途和特點(diǎn)吧
    2023-08-08
  • 利用Golang實(shí)現(xiàn)對(duì)配置文件加密

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

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

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

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

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

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

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

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

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

    這篇文章主要介紹了使用Go語言創(chuàng)建WebSocket服務(wù)的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(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è)置多個(gè)GOPATH的方式

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

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

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

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

最新評(píng)論

特克斯县| 德安县| 化州市| 文水县| 鹤壁市| 渑池县| 通江县| 广元市| 陆良县| 田东县| 新乡市| 海阳市| 漳浦县| 泉州市| 荣成市| 诸暨市| 阳原县| 堆龙德庆县| 体育| 苗栗县| 文昌市| 英山县| 南丰县| 万年县| 台北县| 巴东县| 安平县| 大姚县| 嵊州市| 弥勒县| 鄂伦春自治旗| 周口市| 泽州县| 中牟县| 岳普湖县| 泾阳县| 沽源县| 西平县| 旬阳县| 南陵县| 玉林市|