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