golang select 用法

【golang】select關鍵字用法

 

select是go語言中常用的一個關鍵字,其用法也一直被用作面試題來考覈應聘者。今天,結合代碼來分析下select的主要用法。

首先,我們來從官方文檔看一下有關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左側短變量聲明或賦值未被評估。

  1. 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操作可以進行

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。

Blocking on read...
default go...
Process finished with exit code 0

修改代碼,將default註釋:

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

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

Blocking on read...
ch2 case...
Process finished with exit code 0

接着,繼續修改代碼,將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.000612584s later.
Process finished with exit code 0

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

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

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中的作用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章