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