1. 程式人生 > 實用技巧 >golang 學習筆記 ---select關鍵字用法

golang 學習筆記 ---select關鍵字用法

A "select" statement chooses which of a set of possible send or receive operations will proceed. It looks similar to a "switch" statement but with the cases all referring to communication operations.
一個select語句用來選擇哪個case中的傳送或接收操作可以被立即執行。它類似於switch語句,但是它的case涉及到channel有關的I/O操作。

或者換一種說法,select就是用來監聽和channel有關的IO操作,當 IO 操作發生時,觸發相應的動作。

基本用法

//select基本用法
select {
case <- chan1:
// 如果chan1成功讀到資料,則進行該case處理語句
case chan2 <- 1:
// 如果成功向chan2寫入資料,則進行該case處理語句
default:
// 如果上面都沒有成功,則進入default處理流程

  

官方執行步驟

Execution of a "select" statement proceeds in several steps:

1.For all the cases in the statement, the channel operands of receive operations and the channel and right-hand-side expressions of send statements are evaluated exactly once, in source order, upon entering the "select" statement. The result is a set of channels to receive from or send to, and the corresponding values to send. Any side effects in that evaluation will occur irrespective of which (if any) communication operation is selected to proceed. Expressions on the left-hand side of a RecvStmt with a short variable declaration or assignment are not yet evaluated.
所有channel表示式都會被求值、所有被髮送的表示式都會被求值。求值順序:自上而下、從左到右.
結果是選擇一個傳送或接收的channel,無論選擇哪一個case進行操作,表示式都會被執行。RecvStmt左側短變數宣告或賦值未被評估。

If one or more of the communications can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection. Otherwise, if there is a default case, that case is chosen. If there is no default case, the "select" statement blocks until at least one of the communications can proceed.
如果有一個或多個IO操作可以完成,則Go執行時系統會隨機的選擇一個執行,否則的話,如果有default分支,則執行default分支語句,如果連default都沒有,則select語句會一直阻塞,直到至少有一個IO操作可以進行.
3.Unless the selected case is the default case, the respective communication operation is executed.
除非所選擇的情況是預設情況,否則執行相應的通訊操作。

4.If the selected case is a RecvStmt with a short variable declaration or an assignment, the left-hand side expressions are evaluated and the received value (or values) are assigned.
如果所選case是具有短變數宣告或賦值的RecvStmt,則評估左側表示式並分配接收值(或多個值)。

5.The statement list of the selected case is executed.
執行所選case中的語句

  

案例分析

案例1 如果有一個或多個IO操作可以完成,則Go執行時系統會隨機的選擇一個執行,否則的話,如果有default分支,則執行default分支語句,如果連default都沒有,則select語句會一直阻塞,直到至少有一個IO操作可以進行
package main

import (
	"fmt"
	"time"
)

func main() {
	start := time.Now()
	c := make(chan interface{})
	ch1 := make(chan int)
	ch2 := make(chan int)

	go func() {

		time.Sleep(4 * time.Second)
		close(c)
	}()

	go func() {

		time.Sleep(3 * time.Second)
		ch1 <- 3
	}()

	go func() {

		time.Sleep(3 * time.Second)
		ch2 <- 5
	}()

	fmt.Println("Blocking on read...")
	select {
	case <-c:

		fmt.Printf("Unblocked %v later.\n", time.Since(start))

	case <-ch1:

		fmt.Printf("ch1 case...")
	case <-ch2:

		fmt.Printf("ch1 case...")
	default:

		fmt.Printf("default go...")
	}

}

執行上述程式碼,由於當前時間還未到3s。所以,目前程式會走default。

修改程式碼,將default註釋:

//default:
 //       fmt.Printf("default go...")

這時,select語句會阻塞,直到監測到一個可以執行的IO操作為止。這裡,先會執行完睡眠3s的gorountine,此時兩個channel都滿足條件,這時系統會隨機選擇一個case繼續操作。

輸出:

Blocking on read...
ch1 case...

接著,繼續修改程式碼,將ch的gorountine休眠時間改為5s:

go func() {

        time.Sleep(5*time.Second)
        ch1 <- 3
    }()
go func() {

        time.Sleep(5*time.Second)
        ch2 <- 3
    }()

此時會先執行到上面的gorountine,select執行的就是c的case。

輸出:

Blocking on read...
Unblocked 4.0009959s later.

  

示例2 所有channel表示式都會被求值、所有被髮送的表示式都會被求值。求值順序:自上而下、從左到右.
package main

import (
	"fmt"
	// "time"
)

var ch1 chan int
var ch2 chan int
var chs = []chan int{ch1, ch2}
var numbers = []int{1, 2, 3, 4, 5}

func main() {

	select {
	case getChan(0) <- getNumber(2):

		fmt.Println("1th case is selected.")
	case getChan(1) <- getNumber(3):

		fmt.Println("2th case is selected.")
	default:

		fmt.Println("default!.")
	}
}

func getNumber(i int) int {
	fmt.Printf("numbers[%d]\n", i)

	return numbers[i]
}
func getChan(i int) chan int {
	fmt.Printf("chs[%d]\n", i)

	return chs[i]
}

此時,select語句走的是default操作。但是這時每個case的表示式都會被執行。以case1為例:

case getChan(0) <- getNumber(2):

系統會從左到右先執行getChan函式列印chs[0],然後執行getNumber函式列印numbers[2]。同樣,從上到下分別執行所有case的語句。所以,程式執行的結果為:

chs[0]
numbers[2]
chs[1]
numbers[3]
default!.

Process finished with exit code 0

  

示例3 break關鍵字結束select
package main

import (
	"fmt"
	// "time"
)

func main() {

	ch1 := make(chan int, 1)
	ch2 := make(chan int, 1)

	ch1 <- 3
	ch2 <- 5

	select {
	case <-ch1:

		fmt.Println("ch1 selected.")

		break

		fmt.Println("ch1 selected after break")
	case <-ch2:

		fmt.Println("ch2 selected.")
		fmt.Println("ch2 selected without break")
	}
}

很明顯,ch1和ch2兩個通道都可以讀取到值,所以系統會隨機選擇一個case執行。我們發現選擇執行ch1的case時,由於有break關鍵字只執行了一句:

ch1 selected.

Process finished with exit code 0

但是,當系統選擇ch2的case時,列印結果為:

ch2 selected.
ch2 selected without break

Process finished with exit code 0

如此就顯而易見,break關鍵字在select中的作用。