天天看點

go:select channel源碼參考buffered和unbufferedGo Schedulerhchan結構向buffered channel發送select怎麼搞學到了啥?

參考

go夜讀

select源碼

參考文章

buffered和unbuffered

buffered:

ch <- v happens before v <- ch
           

有buffer的情況下:先把值寫到channel,才能讀到

make(chan interface{}, size) 
           

unbuffered:

v <- ch happens before ch <- v
           

無buffer呢?先讀了,之後才寫。(沒有中間人拿這個東西)

make(chan interface{}, 0)
           

Go Scheduler

每個M P 會有一些本地的go routine隊列,先處理自己的(不用去用鎖搶全局的G隊列)

工作竊取算法:如果自己的搞完了,那就去全局/别人家偷一點玩。

go:select channel源碼參考buffered和unbufferedGo Schedulerhchan結構向buffered channel發送select怎麼搞學到了啥?

hchan結構

是個環形隊列

recvq、sendq:等待的隊列

sudog: 包裝go routine的指針

雙向隊列 prev next

elem: 資料元素

c: 目前阻塞在哪個channel

go:select channel源碼參考buffered和unbufferedGo Schedulerhchan結構向buffered channel發送select怎麼搞學到了啥?

如果是unbuffered的,不配置設定ring buffer

向buffered channel發送

加鎖然後放進ring buffer裡就完事了

go:select channel源碼參考buffered和unbufferedGo Schedulerhchan結構向buffered channel發送select怎麼搞學到了啥?

如果隊列已滿,還要發,那隻能在發送隊列sendq等待:

go:select channel源碼參考buffered和unbufferedGo Schedulerhchan結構向buffered channel發送select怎麼搞學到了啥?

如果來了新的接收方:

go:select channel源碼參考buffered和unbufferedGo Schedulerhchan結構向buffered channel發送select怎麼搞學到了啥?

先出隊;然後把等待的在sendq要寫的東西塞進ringbuffer;goroutine又可以回去等待排程了。

go:select channel源碼參考buffered和unbufferedGo Schedulerhchan結構向buffered channel發送select怎麼搞學到了啥?

入隊等待,park

go:select channel源碼參考buffered和unbufferedGo Schedulerhchan結構向buffered channel發送select怎麼搞學到了啥?

如果接收的時候buf為空,則直接拿走,不經過ringbuffer,跟unbuffered的情況一模一樣。

close:關閉

go:select channel源碼參考buffered和unbufferedGo Schedulerhchan結構向buffered channel發送select怎麼搞學到了啥?

關閉還可以收,但是不能發了。

select怎麼搞

go:select channel源碼參考buffered和unbufferedGo Schedulerhchan結構向buffered channel發送select怎麼搞學到了啥?
go:select channel源碼參考buffered和unbufferedGo Schedulerhchan結構向buffered channel發送select怎麼搞學到了啥?

先理順執行步驟:

首先這種等待喚醒的肯定是一個循環(loop)

然後會需要随機地執行case(随機打亂數組然後按照随機順序執行)

找到有io的case就執行相應的操作(随機打亂後第一個有io的被執行然後傳回)

上面就是主要的步驟了。但是有個問題。這段代碼會被并行執行。肯定需要對select中相關的channel進行加鎖。如果單單是按照随機case去執行加鎖,很可能發生死鎖。

比如?協程1加鎖順序 1、2, 協程2加鎖順序 2、1;

協程1加鎖了1,還沒加鎖2;換到協程2執行,加鎖2,然後想加鎖1失敗,切回協程1,想加鎖2失敗,gg,誰也鎖不上。

如果按照channel位址排序後再順序加鎖則不會發生死鎖,每個協程加鎖順序都是1、2、3,搞不出死鎖的情況。

具體代碼:

這裡會先生成兩個排序,一個是pollorder:選擇case的順序(随機生成);一個是lockorder:鎖定的順序(防止死鎖)。

具體是幹嘛的?

pollorder好了解:就是随機生成輪詢順序。

lockorder是用來指定鎖定順序的。這個不太好了解。具體是怎麼做的呢?

go:select channel源碼參考buffered和unbufferedGo Schedulerhchan結構向buffered channel發送select怎麼搞學到了啥?
go:select channel源碼參考buffered和unbufferedGo Schedulerhchan結構向buffered channel發送select怎麼搞學到了啥?

實際上就是按順序把channel lock上了(注意可能相鄰的是相同的channel)

unlock則是按照相反的順序unlock

go:select channel源碼參考buffered和unbufferedGo Schedulerhchan結構向buffered channel發送select怎麼搞學到了啥?

為什麼需要反向解鎖?感覺挺符合直覺的,但是究竟是為啥?假設不這樣。

協程1: 加鎖 1、2,解鎖 1;

協程2: 加鎖 1、2;

協程1: 解鎖2。

協程2:??我明明加鎖了 WTF?

但如果反向解鎖:

協程1:加鎖1、2,解鎖2;

協程2:加鎖1失敗,讓出

協程1:解鎖1

協程2:加鎖1、2、解鎖2、1。

學到了啥?

當我們需要對一堆東西進行加鎖解鎖的時候,必須大家都按照一樣的順序加鎖,再大家都按照相反的順序解鎖。

go:select channel源碼參考buffered和unbufferedGo Schedulerhchan結構向buffered channel發送select怎麼搞學到了啥?