參考
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隊列)
工作竊取算法:如果自己的搞完了,那就去全局/别人家偷一點玩。

hchan結構
是個環形隊列
recvq、sendq:等待的隊列
sudog: 包裝go routine的指針
雙向隊列 prev next
elem: 資料元素
c: 目前阻塞在哪個channel
如果是unbuffered的,不配置設定ring buffer
向buffered channel發送
加鎖然後放進ring buffer裡就完事了
如果隊列已滿,還要發,那隻能在發送隊列sendq等待:
如果來了新的接收方:
先出隊;然後把等待的在sendq要寫的東西塞進ringbuffer;goroutine又可以回去等待排程了。
入隊等待,park
如果接收的時候buf為空,則直接拿走,不經過ringbuffer,跟unbuffered的情況一模一樣。
close:關閉
關閉還可以收,但是不能發了。
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是用來指定鎖定順序的。這個不太好了解。具體是怎麼做的呢?
實際上就是按順序把channel lock上了(注意可能相鄰的是相同的channel)
unlock則是按照相反的順序unlock
為什麼需要反向解鎖?感覺挺符合直覺的,但是究竟是為啥?假設不這樣。
協程1: 加鎖 1、2,解鎖 1;
協程2: 加鎖 1、2;
協程1: 解鎖2。
協程2:??我明明加鎖了 WTF?
但如果反向解鎖:
協程1:加鎖1、2,解鎖2;
協程2:加鎖1失敗,讓出
協程1:解鎖1
協程2:加鎖1、2、解鎖2、1。
學到了啥?
當我們需要對一堆東西進行加鎖解鎖的時候,必須大家都按照一樣的順序加鎖,再大家都按照相反的順序解鎖。