Go 併發 -- Select

這是『就要學習 Go 語言』系列的第 23 篇分享文章函數

Select 的做用

select 的用法有點相似 switch 語句,但 select 不會有輸入值並且只用於信道操做。select 用於從多個發送或接收信道操做中進行選擇,語句會阻塞直到其中有信道能夠操做,若是有多個信道能夠操做,會隨機選擇其中一個 case 執行。 看下例子:學習

func service1(ch chan string) {
	time.Sleep(2 * time.Second)
	ch <- "from service1"
}
func service2(ch chan string) {
	time.Sleep(1 * time.Second)
	ch <- "from service2"
}

func main() {
	ch1 := make(chan string)
	ch2 := make(chan string)
	go service1(ch1)
	go service2(ch2)
	
	select {       // 會發送阻塞
	case s1 := <-ch1:
		fmt.Println(s1)
	case s2 := <-ch2:
		fmt.Println(s2)
	}
}
複製代碼

輸出:from service2 上面的例子執行到 select 語句的時候回發生阻塞,main 協程等待一個 case 操做可執行,很明顯是 service2 先準備好讀取的數據(休眠 1s),因此輸出 from service2。 看下在兩種操做都準備好的狀況:ui

func service1(ch chan string) {
	//time.Sleep(2 * time.Second)
	ch <- "from service1"
}
func service2(ch chan string) {
	//time.Sleep(1 * time.Second)
	ch <- "from service2"
}

func main() {
	ch1 := make(chan string)
	ch2 := make(chan string)
	go service1(ch1)
	go service2(ch2)

	time.Sleep(2*time.Second)
	select {
	case s1 := <-ch1:
		fmt.Println(s1)
	case s2 := <-ch2:
		fmt.Println(s2)
	}
}
複製代碼

咱們把函數裏的延時註釋掉,主函數 select 以前加 2s 的延時以等待兩個信道的數據準備好,select 會隨機選取其中一個 case 執行,因此輸出也是隨機的。spa

default case

與 switch 語句相似,select 也有 default case,是的 select 語句不在阻塞,若是其餘信道操做尚未準備好,將會直接執行 default 分支。.net

func service1(ch chan string) {
	ch <- "from service1"
}
func service2(ch chan string) {
	ch <- "from service2"
}

func main() {
	ch1 := make(chan string)
	ch2 := make(chan string)
	go service1(ch1)
	go service2(ch2)

	select {         // ch1 ch2 都尚未準備好,直接執行 default 分支 
	case s1 := <-ch1:
		fmt.Println(s1)
	case s2 := <-ch2:
		fmt.Println(s2)
	default:
		fmt.Println("no case ok")
	}
}
複製代碼

輸出:no case ok 執行到 select 語句的時候,因爲信道 ch一、ch2 都沒準備好,直接執行 default 語句。code

func service1(ch chan string) {
	ch <- "from service1"
}
func service2(ch chan string) {
	ch <- "from service2"
}

func main() {
	ch1 := make(chan string)
	ch2 := make(chan string)
	go service1(ch1)
	go service2(ch2)

	time.Sleep(time.Second)   // 延時 1s,等待 ch1 ch2 準備就緒
	
	select {
	case s1 := <-ch1:
		fmt.Println(s1)
	case s2 := <-ch2:
		fmt.Println(s2)
	default:
		fmt.Println("no case ok")
	}
}
複製代碼

在 select 語句以前加了 1s 延時,等待 ch1 ch2 準備就緒。由於兩個通道都準備好了,因此不會走 default 語句。隨機輸出 from service1 或 from service2。cdn

nil channel

信道的默認值是 nil,不能對 nil 信道進行讀寫操做。看下面的例子協程

func service1(ch chan string) {
	ch <- "from service1"
}

func main() {

	var ch chan string
	go service1(ch)
	select {
	case str := <-ch:
		fmt.Println(str)
	}
}
複製代碼

報錯:get

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [select (no cases)]:

goroutine 18 [chan send (nil chan)]:
複製代碼

有兩個錯誤的地方須要注意: [select (no cases)] case 分支中若是信道是 nil,該分支就會被忽略,那麼上面就變成空 select{} 語句,阻塞主協程,調度 service1 協程,在 nil 信道上操做,便報[chan send (nil chan)] 錯誤。可使用上面的 default case 避免發生這樣的錯誤。string

func service1(ch chan string) {
	ch <- "from service1"
}

func main() {

	var ch chan string
	go service1(ch)
	select {
	case str := <-ch:
		fmt.Println(str)
	default:
		fmt.Println("I am default")
	}
}
複製代碼

輸出:I am default

添加超時時間

有時候,咱們不但願當即執行 default 語句,而是但願等待一段時間,若這個時間段內尚未可操做的信道,則執行規定的語句。能夠在 case 語句後面設置超時時間。

func service1(ch chan string) {
	time.Sleep(5 * time.Second)
	ch <- "from service1"
}
func service2(ch chan string) {
	time.Sleep(3 * time.Second)
	ch <- "from service2"
}

func main() {
	ch1 := make(chan string)
	ch2 := make(chan string)
	go service1(ch1)
	go service2(ch2)

	select {       // 會發送阻塞
	case s1 := <-ch1:
		fmt.Println(s1)
	case s2 := <-ch2:
		fmt.Println(s2)
	case <-time.After(2*time.Second):     // 等待 2s
		fmt.Println("no case ok")
	}
}
複製代碼

輸出:no case ok 在第三個 case 語句中設置了 2s 的超時時間,這 2s 內若是其餘可操做的信道,便會執行該 case。

空 select

package main

func main() {  
    select {}
}
複製代碼

咱們知道 select 語句會發生阻塞,直到有 case 能夠操做。可是空 select 語句沒有 case 分支,因此便一直阻塞引發死鎖。 報錯:

fatal error: all goroutines are asleep - deadlock!
goroutine 1 [select (no cases)]
複製代碼

但願這篇文章可以幫助你,Good day!


(全文完)

原創文章,若需轉載請註明出處!
歡迎掃碼關注公衆號「Golang來啦」或者移步 seekload.net ,查看更多精彩文章。

給你準備了學習 Go 語言相關書籍,公號後臺回覆【電子書】領取!

公衆號二維碼
相關文章
相關標籤/搜索