select 語句使得一個 goroutine 在多個通訊操作上等待。
湖南網(wǎng)站建設(shè)公司創(chuàng)新互聯(lián),湖南網(wǎng)站設(shè)計制作,有大型網(wǎng)站制作公司豐富經(jīng)驗。已為湖南近千家提供企業(yè)網(wǎng)站建設(shè)服務(wù)。企業(yè)網(wǎng)站搭建\外貿(mào)營銷網(wǎng)站建設(shè)要多少錢,請找那個售后服務(wù)好的湖南做網(wǎng)站的公司定做!
select 會阻塞,直到條件分支中的某個可以繼續(xù)執(zhí)行,這時就會執(zhí)行那個條件分支。當(dāng)多個都準(zhǔn)備好的時候,會隨機(jī)選擇一個。
復(fù)制代碼代碼如下:
package main
import "fmt"
func fibonacci(c, quit chan int) {
x, y := 1, 1
for {
select {
case c - x:
x, y = y, x + y
case -quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i 10; i++ {
fmt.Println(-c)
}
quit - 0
}()
fibonacci(c, quit)
}
默認(rèn)選擇
當(dāng) select 中的其他條件分支都沒有準(zhǔn)備好的時候,default 分支會被執(zhí)行。
為了非阻塞的發(fā)送或者接收,可使用 default 分支:
select {
case i := -c:
// use i
default:
// receiving from c would block
}
復(fù)制代碼代碼如下:
package main
import (
"fmt"
"time"
)
func main() {
tick := time.Tick(1e8)
boom := time.After(5e8)
for {
select {
case -tick:
fmt.Println("tick.")
case -boom:
fmt.Println("BOOM!")
return
default:
fmt.Println(" .")
time.Sleep(5e7)
}
}
}
有數(shù)量不定的goroutine往channel里塞東西,然后select來接收并處理。如果所有的goroutine都完成工作,ch也接收完了,那么select就會阻塞?,F(xiàn)在我想要跳出死循環(huán),大概是在for循環(huán)里設(shè)置一些東西,不知道可不可以實現(xiàn),或者有類似的解決方法。
go func(){ for{ select{ case v:= 《-ch: //這里打左尖括號排版就會亂,不知道是不是網(wǎng)站的bug DoSomething() } } }()
Go里面提供了一個關(guān)鍵字select,通過select可以監(jiān)聽channel上的數(shù)據(jù)流動。
select的用法與switch語言非常類似,由select開始一個新的選擇塊,每個選擇條件由case語句來描述。
與switch語句相比, select有比較多的限制,其中最大的一條限制就是每個case語句里必須是一個IO操作,大致的結(jié)構(gòu)如下:
在一個select語句中,Go語言會按順序從頭至尾評估每一個發(fā)送和接收的語句。
如果其中的任意一語句可以繼續(xù)執(zhí)行(即沒有被阻塞),那么就從那些可以執(zhí)行的語句中任意選擇一條來使用。
如果沒有任意一條語句可以執(zhí)行(即所有的通道都被阻塞),那么有兩種可能的情況:
如果給出了default語句,那么就會執(zhí)行default語句,同時程序的執(zhí)行會從select語句后的語句中恢復(fù)。
如果沒有default語句,那么select語句將被阻塞,直到至少有一個通信可以進(jìn)行下去
有時候會出現(xiàn)goroutine阻塞的情況,那么我們?nèi)绾伪苊庹麄€程序進(jìn)入阻塞的情況呢?我們可以利用select來設(shè)置超時,通過如下的方式實現(xiàn):
select總結(jié):
作用: 用來監(jiān)聽 channel 上的數(shù)據(jù)流動方向。 讀?寫?
select實現(xiàn)fibonacci數(shù)列:
Go 的select語句是一種僅能用于channl發(fā)送和接收消息的專用語句,此語句運行期間是阻塞的;當(dāng)select中沒有case語句的時候,會阻塞當(dāng)前的groutine。所以,有人也會說select是用來阻塞監(jiān)聽goroutine的。
還有人說:select是Golang在語言層面提供的I/O多路復(fù)用的機(jī)制,其專門用來檢測多個channel是否準(zhǔn)備完畢:可讀或可寫。
以上說法都正確。
我們來回顧一下是什么是 I/O多路復(fù)用 。
每來一個進(jìn)程,都會建立連接,然后阻塞,直到接收到數(shù)據(jù)返回響應(yīng)。
普通這種方式的缺點其實很明顯:系統(tǒng)需要創(chuàng)建和維護(hù)額外的線程或進(jìn)程。因為大多數(shù)時候,大部分阻塞的線程或進(jìn)程是處于等待狀態(tài),只有少部分會接收并處理響應(yīng),而其余的都在等待。系統(tǒng)為此還需要多做很多額外的線程或者進(jìn)程的管理工作。
為了解決圖中這些多余的線程或者進(jìn)程,于是有了"I/O多路復(fù)用"
每個線程或者進(jìn)程都先到圖中”裝置“中注冊,然后阻塞,然后只有一個線程在”運輸“,當(dāng)注冊的線程或者進(jìn)程準(zhǔn)備好數(shù)據(jù)后,”裝置“會根據(jù)注冊的信息得到相應(yīng)的數(shù)據(jù)。從始至終kernel只會使用圖中這個黃黃的線程,無需再對額外的線程或者進(jìn)程進(jìn)行管理,提升了效率。
select的實現(xiàn)經(jīng)歷了多個版本的修改,當(dāng)前版本為:1.11
select這個語句底層實現(xiàn)實際上主要由兩部分組成: case語句 和 執(zhí)行函數(shù) 。
源碼地址為:/go/src/runtime/select.go
每個case語句,單獨抽象出以下結(jié)構(gòu)體:
結(jié)構(gòu)體可以用下圖表示:
然后執(zhí)行select語句實際上就是調(diào)用 func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool) 函數(shù)。
func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool) 函數(shù)參數(shù):
selectgo 返回所選scase的索引(該索引與其各自的select {recv,send,default}調(diào)用的序號位置相匹配)。此外,如果選擇的scase是接收操作(recv),則返回是否接收到值。
誰負(fù)責(zé)調(diào)用 func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool) 函數(shù)呢?
在 /reflect/value.go 中有個 func rselect([]runtimeSelect) (chosen int, recvOK bool) 函數(shù),此函數(shù)的實現(xiàn)在 /runtime/select.go 文件中的 func reflect_rselect(cases []runtimeSelect) (int, bool) 函數(shù)中:
那誰調(diào)用的 func rselect([]runtimeSelect) (chosen int, recvOK bool) 呢?
在 /refect/value.go 中,有一個 func Select(cases []SelectCase) (chosen int, recv Value, recvOK bool) 的函數(shù),其調(diào)用了 rselect 函數(shù),并將最終Go中select語句的返回值的返回。
以上這三個函數(shù)的調(diào)用棧按順序如下:
這仨函數(shù)中無論是返回值還是參數(shù)都大同小異,可以簡單粗暴的認(rèn)為:函數(shù)參數(shù)傳入的是case語句,返回值返回被選中的case語句。
那誰調(diào)用了 func Select(cases []SelectCase) (chosen int, recv Value, recvOK bool) 呢?
可以簡單的認(rèn)為是系統(tǒng)了。
來個簡單的圖:
前兩個函數(shù) Select 和 rselect 都是做了簡單的初始化參數(shù),調(diào)用下一個函數(shù)的操作。select真正的核心功能,是在最后一個函數(shù) func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool) 中實現(xiàn)的。
打亂傳入的case結(jié)構(gòu)體順序
鎖住其中的所有的channel
遍歷所有的channel,查看其是否可讀或者可寫
如果其中的channel可讀或者可寫,則解鎖所有channel,并返回對應(yīng)的channel數(shù)據(jù)
假如沒有channel可讀或者可寫,但是有default語句,則同上:返回default語句對應(yīng)的scase并解鎖所有的channel。
假如既沒有channel可讀或者可寫,也沒有default語句,則將當(dāng)前運行的groutine阻塞,并加入到當(dāng)前所有channel的等待隊列中去。
然后解鎖所有channel,等待被喚醒。
此時如果有個channel可讀或者可寫ready了,則喚醒,并再次加鎖所有channel,
遍歷所有channel找到那個對應(yīng)的channel和G,喚醒G,并將沒有成功的G從所有channel的等待隊列中移除。
如果對應(yīng)的scase值不為空,則返回需要的值,并解鎖所有channel
如果對應(yīng)的scase為空,則循環(huán)此過程。
在想想select和channel做了什么事兒,我覺得和多路復(fù)用是一回事兒