首頁 > 軟體

golang中select語句的簡單範例

2022-06-29 18:02:46

前言

在golang語言中,select語句 就是用來監聽和channel有關的IO操作,當IO操作發生時,觸發相應的case動作。有了 select語句,可以實現 main主執行緒 與 goroutine執行緒 之間的互動。

select {
    case <-ch1 :     // 檢測有沒有資料可讀
        // 一旦成功讀取到資料,則進行該case處理語句
    case ch2 <- 1 :  // 檢測有沒有資料可寫
        // 一旦成功向ch2寫入資料,則進行該case處理語句
    default:
        // 如果以上都沒有符合條件,那麼進入default處理流程
}

注意事項:

  • select語句 只能用於channel通道的IO操作,每個case都必須是一個通道。
  • 如果不設定 default條件,當沒有IO操作發生時,select語句就會一直阻塞;
  • 如果有一個或多個IO操作發生時,Go執行時會隨機選擇一個case執行,但此時將無法保證執行順序;
  • 對於case語句,如果存在通道值為nil的讀寫操作,則該分支將被忽略,可以理解為相當於從select語句中刪除了這個case;
  • 對於空的 select語句,會引起死鎖;
  • 對於在 for中的select語句,不能新增 default,否則會引起cpu佔用過高的問題;

1.先舉個簡單例子

先建立兩個通道,並在 select 前往 c2 傳送資料

package main
 
import (
	"fmt"
)
 
//go的通道選擇器 讓你可以同時等待多個通道操作。go協程和通道以及選擇器的結合是go的一個強大特性。
 
func main() {
	// 在我們的例子中,我們將從兩個通道中選擇。
	c1 := make(chan string, 1)
	c2 := make(chan string, 1)
 
	c2 <- "nihao"
 
	//go func() {
	//	time.Sleep(time.Second * 1)
	//	c1 <- "one"
	//}()
	//
	//go func() {
	//	time.Sleep(time.Second * 2)
	//	c2 <- "two"
	//}()
 
	//我們使用 `select` 關鍵字來同時等待這兩個值,並列印各自接收到的值。
	//for i := 0; i < 2; i++ {
	select {
	case msg1 := <-c1:
		fmt.Println("received", msg1)
	case msg2 := <-c2:
		fmt.Println("received", msg2)
	default:
		fmt.Println("No data received")
	}
	//}
 
}

在執行 select 時,會遍歷所有(如果有機會的話)的 case 表示式,只要有一個通道有接收到資料,那麼 select 就結束,所以輸出如下

2. 避免造成死鎖

select 在執行過程中,必須命中其中的某一分支。

如果在遍歷完所有的 case 後,若沒有命中(命中:也許這樣描述不太準確,我本意是想說可以執行通道的操作語句)任何一個 case 表示式,就會進入 default 裡的程式碼分支。

package main
 
import (
	"fmt"
)
 
//go的通道選擇器 讓你可以同時等待多個通道操作。go協程和通道以及選擇器的結合是go的一個強大特性。
 
func main() {
	// 在我們的例子中,我們將從兩個通道中選擇。
	c1 := make(chan string, 1)
	c2 := make(chan string, 1)
 
	//c2 <- "nihao"
 
	//go func() {
	//	time.Sleep(time.Second * 1)
	//	c1 <- "one"
	//}()
	//
	//go func() {
	//	time.Sleep(time.Second * 2)
	//	c2 <- "two"
	//}()
 
	//我們使用 `select` 關鍵字來同時等待這兩個值,並列印各自接收到的值。
	//for i := 0; i < 2; i++ {
	select {
	case msg1 := <-c1:
		fmt.Println("received", msg1)
	case msg2 := <-c2:
		fmt.Println("received", msg2)
		//default:
		//	fmt.Println("No data received")
		//}
	}
}

 但如果你沒有寫 default 分支,select 就會阻塞,直到有某個 case 可以命中,而如果一直沒有命中,select 就會丟擲 deadlock 的錯誤,就像下面這樣子。

1.解決這個問題的方法有兩種

一個是,養成好習慣,在 select 的時候,也寫好 default 分支程式碼,儘管你 default 下沒有寫任何程式碼。

 另一個是,讓其中某一個通道可以接收到資料

3. select 隨機性

之前學過 switch 的時候,知道了 switch 裡的 case 是順序執行的,但在 select 裡卻不是。

通過下面這個例子的執行結果就可以看出

4. select 的超時

當 case 裡的通道始終沒有接收到資料時,而且也沒有 default 語句時,select 整體就會阻塞,但是有時我們並不希望 select 一直阻塞下去,這時候就可以手動設定一個超時時間。

5. 讀取/寫入都可以

上面例子裡的 case,好像都只從通道中讀取資料,但實際上,select 裡的 case 表示式只要求你是對通道的操作即可,不管你是往通道寫入資料,還是從通道讀出資料。

6. 總結一下

select 與 switch 原理很相似,但它的使用場景更特殊,學習了本篇文章,你需要知道如下幾點區別:

  1. select 只能用於 channel 的操作(寫入/讀出),而 switch 則更通用一些;
  2. select 的 case 是隨機的,而 switch 裡的 case 是順序執行;
  3. select 要注意避免出現死鎖,同時也可以自行實現超時機制;
  4. select 裡沒有類似 switch 裡的 fallthrough 的用法;
  5. select 不能像 switch 一樣接函數或其他表示式。

到此這篇關於golang中select語句的文章就介紹到這了,更多相關go中select語句內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com