go io.reader 多次读取_Go 经典入门系列 24:Select
歡迎來到 Golang 系列教程[1]的第 24 篇。
什么是 select?
select 語句用于在多個發(fā)送/接收信道操作中進行選擇。select 語句會一直阻塞,直到發(fā)送/接收操作準備就緒。如果有多個信道操作準備完畢,select 會隨機地選取其中之一執(zhí)行。該語法與 switch 類似,所不同的是,這里的每個 case 語句都是信道操作。我們好好看一些代碼來加深理解吧。
示例
package?mainimport?(
????"fmt"
????"time"
)
func?server1(ch?chan?string)?{
????time.Sleep(6?*?time.Second)
????ch?"from?server1"
}
func?server2(ch?chan?string)?{
????time.Sleep(3?*?time.Second)
????ch?"from?server2"
}
func?main()?{
????output1?:=?make(chan?string)
????output2?:=?make(chan?string)
????go?server1(output1)
????go?server2(output2)
????select?{
????case?s1?:=?????????fmt.Println(s1)
????case?s2?:=?????????fmt.Println(s2)
????}
}
在線運行程序[2]
在上面程序里,server1 函數(shù)(第 8 行)休眠了 6 秒,接著將文本 from server1 寫入信道 ch。而 server2 函數(shù)(第 12 行)休眠了 3 秒,然后把 from server2 寫入了信道 ch。
而 main 函數(shù)在第 20 行和第 21 行,分別調(diào)用了 server1 和 server2 兩個 Go 協(xié)程。
在第 22 行,程序運行到了 select 語句。select 會一直發(fā)生阻塞,除非其中有 case 準備就緒。在上述程序里,server1 協(xié)程會在 6 秒之后寫入 output1 信道,而server2 協(xié)程在 3 秒之后就寫入了 output2 信道。因此 select 語句會阻塞 3 秒鐘,等著 server2 向 output2 信道寫入數(shù)據(jù)。3 秒鐘過后,程序會輸出:
from?server2然后程序終止。
select 的應(yīng)用
在上面程序中,函數(shù)之所以取名為 server1 和 server2,是為了展示 select 的實際應(yīng)用。
假設(shè)我們有一個關(guān)鍵性應(yīng)用,需要盡快地把輸出返回給用戶。這個應(yīng)用的數(shù)據(jù)庫復(fù)制并且存儲在世界各地的服務(wù)器上。假設(shè)函數(shù) server1 和 server2 與這樣不同區(qū)域的兩臺服務(wù)器進行通信。每臺服務(wù)器的負載和網(wǎng)絡(luò)時延決定了它的響應(yīng)時間。我們向兩臺服務(wù)器發(fā)送請求,并使用 select 語句等待相應(yīng)的信道發(fā)出響應(yīng)。select 會選擇首先響應(yīng)的服務(wù)器,而忽略其它的響應(yīng)。使用這種方法,我們可以向多個服務(wù)器發(fā)送請求,并給用戶返回最快的響應(yīng)了。:)
默認情況
在沒有 case 準備就緒時,可以執(zhí)行 select 語句中的默認情況(Default Case)。這通常用于防止 select 語句一直阻塞。
package?mainimport?(
????"fmt"
????"time"
)
func?process(ch?chan?string)?{
????time.Sleep(10500?*?time.Millisecond)
????ch?"process?successful"
}
func?main()?{
????ch?:=?make(chan?string)
????go?process(ch)
????for?{
????????time.Sleep(1000?*?time.Millisecond)
????????select?{
????????case?v?:=?????????????fmt.Println("received?value:?",?v)
????????????return
????????default:
????????????fmt.Println("no?value?received")
????????}
????}
}
在線運行程序[3]
上述程序中,第 8 行的 process 函數(shù)休眠了 10500 毫秒(10.5 秒),接著把 process successful 寫入 ch 信道。在程序中的第 15 行,并發(fā)地調(diào)用了這個函數(shù)。
在并發(fā)地調(diào)用了 process 協(xié)程之后,主協(xié)程啟動了一個無限循環(huán)。這個無限循環(huán)在每一次迭代開始時,都會先休眠 1000 毫秒(1 秒),然后執(zhí)行一個 select 操作。在最開始的 10500 毫秒中,由于 process 協(xié)程在 10500 毫秒后才會向 ch 信道寫入數(shù)據(jù),因此 select 語句的第一個 case(即 case v := )并未就緒。所以在這期間,程序會執(zhí)行默認情況,該程序會打印 10 次 no value received。
在 10.5 秒之后,process 協(xié)程會在第 10 行向 ch 寫入 process successful。現(xiàn)在,就可以執(zhí)行 select 語句的第一個 case 了,程序會打印 received value: process successful,然后程序終止。該程序會輸出:
no?value?receivedno?value?received
no?value?received
no?value?received
no?value?received
no?value?received
no?value?received
no?value?received
no?value?received
no?value?received
received?value:??process?successful
死鎖與默認情況
package?mainfunc?main()?{
????ch?:=?make(chan?string)
????select?{
????case?????}
}
在線運行程序[4]
上面的程序中,我們在第 4 行創(chuàng)建了一個信道 ch。我們在 select 內(nèi)部(第 6 行),試圖讀取信道 ch。由于沒有 Go 協(xié)程向該信道寫入數(shù)據(jù),因此 select 語句會一直阻塞,導(dǎo)致死鎖。該程序會觸發(fā)運行時 panic,報錯信息如下:
fatal?error:?all?goroutines?are?asleep?-?deadlock!goroutine?1?[chan?receive]:
main.main()
????/tmp/sandbox416567824/main.go:6?+0x80
如果存在默認情況,就不會發(fā)生死鎖,因為在沒有其他 case 準備就緒時,會執(zhí)行默認情況。我們用默認情況重寫后,程序如下:
package?mainimport?"fmt"
func?main()?{
????ch?:=?make(chan?string)
????select?{
????case?????default:
????????fmt.Println("default?case?executed")
????}
}
在線運行程序[5]
以上程序會輸出:
default?case?executed如果 select 只含有值為 nil 的信道,也同樣會執(zhí)行默認情況。
package?mainimport?"fmt"
func?main()?{
????var?ch?chan?string
????select?{
????case?v?:=?????????fmt.Println("received?value",?v)
????default:
????????fmt.Println("default?case?executed")
????}
}
在線運行程序[6]
在上面程序中,ch 等于 nil,而我們試圖在 select 中讀取 ch(第 8 行)。如果沒有默認情況,select 會一直阻塞,導(dǎo)致死鎖。由于我們在 select 內(nèi)部加入了默認情況,程序會執(zhí)行它,并輸出:
default?case?executed隨機選取
當(dāng) select 由多個 case 準備就緒時,將會隨機地選取其中之一去執(zhí)行。
package?mainimport?(
????"fmt"
????"time"
)
func?server1(ch?chan?string)?{
????ch?"from?server1"
}
func?server2(ch?chan?string)?{
????ch?"from?server2"
}
func?main()?{
????output1?:=?make(chan?string)
????output2?:=?make(chan?string)
????go?server1(output1)
????go?server2(output2)
????time.Sleep(1?*?time.Second)
????select?{
????case?s1?:=?????????fmt.Println(s1)
????case?s2?:=?????????fmt.Println(s2)
????}
}
在線運行程序[7]
在上面程序里,我們在第 18 行和第 19 行分別調(diào)用了 server1 和 server2 兩個 Go 協(xié)程。接下來,主程序休眠了 1 秒鐘(第 20 行)。當(dāng)程序控制到達第 21 行的 select 語句時,server1 已經(jīng)把 from server1 寫到了 output1 信道上,而 server2 也同樣把 from server2 寫到了 output2 信道上。因此這個 select 語句中的兩種情況都準備好執(zhí)行了。如果你運行這個程序很多次的話,輸出會是 from server1 或者 from server2,這會根據(jù)隨機選取的結(jié)果而變化。
請在你的本地系統(tǒng)上運行這個程序,獲得程序的隨機結(jié)果。因為如果你在 playground 上在線運行的話,它的輸出總是一樣的,這是由于 playground 不具有隨機性所造成的。
這下我懂了:空 select
package?mainfunc?main()?{
????select?{}
}
在線運行程序[8]
你認為上面代碼會輸出什么?
我們已經(jīng)知道,除非有 case 執(zhí)行,select 語句就會一直阻塞著。在這里,select 語句沒有任何 case,因此它會一直阻塞,導(dǎo)致死鎖。該程序會觸發(fā) panic,輸出如下:
fatal?error:?all?goroutines?are?asleep?-?deadlock!goroutine?1?[select?(no?cases)]:
main.main()
????/tmp/sandbox299546399/main.go:4?+0x20
本教程到此結(jié)束。祝你愉快。
上一教程 - 緩沖信道和工作池
下一教程 - Mutex[9]
推薦閱讀
Go 經(jīng)典入門系列 23:緩沖信道和工作池
總結(jié)
以上是生活随笔為你收集整理的go io.reader 多次读取_Go 经典入门系列 24:Select的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 招集令放款中稳不稳 要注意这些才不会翻车
- 下一篇: 不知道本金怎么算利息