淺析Go語言如何在select語句中實現(xiàn)優(yōu)先級
Go 語言中的 select
語句用于監(jiān)控并選擇一組case
語句執(zhí)行相應(yīng)的代碼。它看起來類似于switch
語句,但是select
語句中所有case
中的表達式都必須是channel
的發(fā)送或接收操作。一個典型的select
使用示例如下
select { case <-ch1: fmt.Println("liwenzhou.com") case ch2 <- 1: fmt.Println("q1mi") }
Go 語言中的 select
關(guān)鍵字也能夠讓當(dāng)前 goroutine
同時等待ch1
的可讀和ch2
的可寫,在ch1
和ch2
狀態(tài)改變之前,select
會一直阻塞下去,直到其中的一個 channel
轉(zhuǎn)為就緒狀態(tài)時執(zhí)行對應(yīng)case
分支的代碼。如果多個channel
同時就緒的話則隨機選擇一個case
執(zhí)行。
除了上面展示的典型示例外,接下來我們逐一介紹一些select
的特殊示例。
空select
空select
指的是內(nèi)部不包含任何case
,例如:
select{ }
空的 select
語句會直接阻塞當(dāng)前的goroutine
,使得該goroutine
進入無法被喚醒的永久休眠狀態(tài)。
只有一個case
如果select
中只包含一個case
,那么該select
就變成了一個阻塞的channel
讀/寫操作。
select { case <-ch1: fmt.Println("liwenzhou.com") }
上面的代碼,當(dāng)ch1
可讀時會執(zhí)行打印操作,否則就會阻塞。
有default語句
如果select
中還可以包含default
語句,用于當(dāng)其他case
都不滿足時執(zhí)行一些默認操作。
select { case <-ch1: fmt.Println("liwenzhou.com") default: time.Sleep(time.Second) }
上面的代碼,當(dāng)ch1
可讀時會執(zhí)行打印操作,否則就執(zhí)行default
語句中的代碼,這里就相當(dāng)于做了一個非阻塞的channel
讀取操作。
總結(jié)
select
不存在任何的case
:永久阻塞當(dāng)前 goroutineselect
只存在一個case
:阻塞的發(fā)送/接收select
存在多個case
:隨機選擇一個滿足條件的case
執(zhí)行select
存在default
,其他case
都不滿足時:執(zhí)行default
語句中的代碼
如何在select中實現(xiàn)優(yōu)先級
已知,當(dāng)select
存在多個 case
時會隨機選擇一個滿足條件的case
執(zhí)行。
現(xiàn)在我們有一個需求:我們有一個函數(shù)會持續(xù)不間斷地從ch1
和ch2
中分別接收任務(wù)1和任務(wù)2, 如何確保當(dāng)ch1
和ch2
同時達到就緒狀態(tài)時,優(yōu)先執(zhí)行任務(wù)1,在沒有任務(wù)1的時候再去執(zhí)行任務(wù)2呢?
高級Go語言程序員小明撓了撓頭寫出了如下函數(shù):
func worker(ch1, ch2 <-chan int, stopCh chan struct{}) { for { select { case <-stopCh: return case job1 := <-ch1: fmt.Println(job1) default: select { case job2 := <-ch2: fmt.Println(job2) default: } } } }
上面的代碼通過嵌套兩個select
實現(xiàn)了"優(yōu)先級",看起來是滿足題目要求的。但是這代碼有點問題,如果ch1
和ch2
都沒有達到就緒狀態(tài)的話,整個程序不會阻塞而是進入了死循環(huán)。
怎么辦呢?
小明又撓了撓頭,又寫下了另一個解決方案:
func worker2(ch1, ch2 <-chan int, stopCh chan struct{}) { for { select { case <-stopCh: return case job1 := <-ch1: fmt.Println(job1) case job2 := <-ch2: priority: for { select { case job1 := <-ch1: fmt.Println(job1) default: break priority } } fmt.Println(job2) } } }
這一次,小明不僅使用了嵌套的select
,還組合使用了for
循環(huán)和LABEL
來實現(xiàn)題目的要求。上面的代碼在外層select
選中執(zhí)行job2 := <-ch2
時,進入到內(nèi)層select
循環(huán)繼續(xù)嘗試執(zhí)行job1 := <-ch1
,當(dāng)ch1
就緒時就會一直執(zhí)行,否則跳出內(nèi)層select
。
實際應(yīng)用場景
上面的需求雖然是我編的,但是關(guān)于在select
中實現(xiàn)優(yōu)先級在實際生產(chǎn)中是有實際應(yīng)用場景的,例如K8s的controller中就有關(guān)于上面這個技巧的實際使用示例,這里在關(guān)于select
中實現(xiàn)優(yōu)先級相關(guān)代碼的關(guān)鍵處都已添加了注釋,具體邏輯這里就不展開細說了。
// kubernetes/pkg/controller/nodelifecycle/scheduler/taint_manager.go func (tc *NoExecuteTaintManager) worker(worker int, done func(), stopCh <-chan struct{}) { defer done() // 當(dāng)處理具體事件的時候,我們會希望 Node 的更新操作優(yōu)先于 Pod 的更新 // 因為 NodeUpdates 與 NoExecuteTaintManager無關(guān)應(yīng)該盡快處理 // -- 我們不希望用戶(或系統(tǒng))等到PodUpdate隊列被耗盡后,才開始從受污染的Node中清除pod。 for { select { case <-stopCh: return case nodeUpdate := <-tc.nodeUpdateChannels[worker]: tc.handleNodeUpdate(nodeUpdate) tc.nodeUpdateQueue.Done(nodeUpdate) case podUpdate := <-tc.podUpdateChannels[worker]: // 如果我們發(fā)現(xiàn)了一個 Pod 需要更新,我么你需要先清空 Node 隊列. priority: for { select { case nodeUpdate := <-tc.nodeUpdateChannels[worker]: tc.handleNodeUpdate(nodeUpdate) tc.nodeUpdateQueue.Done(nodeUpdate) default: break priority } } // 在 Node 隊列清空后我們再處理 podUpdate. tc.handlePodUpdate(podUpdate) tc.podUpdateQueue.Done(podUpdate) } } }
到此這篇關(guān)于淺析Go語言如何在select語句中實現(xiàn)優(yōu)先級的文章就介紹到這了,更多相關(guān)Go select優(yōu)先級內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
B站新一代 golang規(guī)則引擎gengine基礎(chǔ)語法
這篇文章主要為大家介紹了B站新一代 golang規(guī)則引擎gengine基礎(chǔ)語法,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-12-12golang高并發(fā)限流操作 ping / telnet
這篇文章主要介紹了golang高并發(fā)限流操作 ping / telnet,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12golang hack插件開發(fā)動態(tài)鏈接庫實例探究
這篇文章主要為大家介紹了golang hack插件開發(fā)動態(tài)鏈接庫實例探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2024-01-01