1. 程式人生 > >GO select用法詳解

GO select用法詳解

原文】golang 的 select 就是監聽 IO 操作,當 IO 操作發生時,觸發相應的動作。 

在執行select語句的時候,執行時系統會自上而下地判斷每個case中的傳送或接收操作是否可以被立即執行(立即執行:意思是當前Goroutine不會因此操作而被阻塞)

select的用法與switch非常類似,由select開始一個新的選擇塊,每個選擇條件由case語句來描述。與switch語句可以選擇任何可使用相等比較的條件相比,select有比較多的限制,其中最大的一條限制就是每個case語句裡必須是一個IO操作,確切的說,應該是一個面向channel的IO操作。

下面這段話來自官方文件:

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 SendStmt:

  //statements

case RecvStmt:

  //statements

default:

  //statements

}

其中,

SendStmt : channelVariable <- value

RecvStmt : variable <-channelVariable

 

A case with a RecvStmt may assign the result of a RecvExpr to one or two variables, which may be declared using a short variable declaration

(IdentifierList := value). The RecvExpr must be a (possibly parenthesized) receive operation(<-channelVariable). There can be at most one default case and it may appear anywhere in the list of cases.

 

示例:

 

    ch1 := make(chan int, 1)
    ch2 := make(chan int, 1)
 
    ch1 <- 1
 
    select {
    case e1 := <-ch1:
        //如果ch1通道成功讀取資料,則執行該case處理語句
        fmt.Printf("1th case is selected. e1=%v", e1)
    case e2 := <-ch2:
        //如果ch2通道成功讀取資料,則執行該case處理語句
        fmt.Printf("2th case is selected. e2=%v", e2)
    default:
        //如果上面case都沒有成功,則進入default處理流程
        fmt.Println("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.(所有channel表示式都會被求值、所有被髮送的表示式都會被求值。求值順序:自上而下、從左到右)

2、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.

5、The statement list of the selected case is executed.

 

Since communication on nil channels can never proceed, a select with only nil channels and no default case blocks forever.

 

可以使用break語句來終止select語句的執行。

 

示例1:select語句會一直等待,直到某個case裡的IO操作可以進行

//main.go

 

package main
 
import "fmt"
import "time"
 
func f1(ch chan int) {
    time.Sleep(time.Second * 5)
    ch <- 1
}
 
func f2(ch chan int) {
    time.Sleep(time.Second * 10)
    ch <- 1
}
 
func main() {
    var ch1 = make(chan int)
    var ch2 = make(chan int)
    go f1(ch1)
    go f2(ch2)
    select {
    case <-ch1:
        fmt.Println("The first case is selected.")
    case <-ch2:
        fmt.Println("The second case is selected.")
    }
}

編譯執行:

 

 

C:/go/bin/go.exe run test14.go [E:/project/go/proj/src/test]

The first case is selected.

成功: 程序退出程式碼 0.

 

示例2:所有跟在case關鍵字右邊的傳送語句或接收語句中的通道表示式和元素表示式都會先被求值。無論它們所在的case是否有可能被選擇都會這樣。 

求值順序:自上而下、從左到右 

此示例使用空值channel進行驗證。

//main.go

 

package main
 
import (
    "fmt"
)
 
//定義幾個變數,其中chs和numbers分別代表通道列表和整數列表
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]
}
 

編譯執行:

 

 

C:/go/bin/go.exe run test4.go [E:/project/go/proj/src/test]

chs[0]

numbers[2]

chs[1]

numbers[3]

default!.

成功: 程序退出程式碼 0.

上面的案例,之所以輸出default!.是因為chs[0]和chs[1]都是空值channel,和空值channel通訊永遠都不會成功。

 

示例3:所有跟在case關鍵字右邊的傳送語句或接收語句中的通道表示式和元素表示式都會先被求值。無論它們所在的case是否有可能被選擇都會這樣。 

求值順序:自上而下、從左到右 

此示例使用非空值channel進行驗證。

//main.go

 

package main
 
import (
    "fmt"
)
 
//定義幾個變數,其中chs和numbers分別代表通道列表和整數列表
var ch1 chan int = make(chan int, 1)  //宣告並初始化channel變數
var ch2 chan int = make(chan int, 1)  //宣告並初始化channel變數
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]
}
 
編譯執行:
 

C:/go/bin/go.exe run test4.go [E:/project/go/proj/src/test]

chs[0]

numbers[2]

chs[1]

numbers[3]

1th case is selected.

成功: 程序退出程式碼 0.

此示例,使用非空值channel進行IO操作,所以可以成功,沒有走default分支。

 
示例4:如果有多個case同時可以執行,go會隨機選擇一個case執行
//main.go
 
package main
 
import (
    "fmt"
)
 
func main() {
    chanCap := 5
    ch := make(chan int, chanCap) //建立channel,容量為5
 
    for i := 0; i < chanCap; i++ { //通過for迴圈,向channel裡填滿資料
        select { //通過select隨機的向channel裡追加資料
        case ch <- 1:
        case ch <- 2:
        case ch <- 3:
        }
    }
 
    for i := 0; i < chanCap; i++ {
        fmt.Printf("%v\n", <-ch)
    }
}
 

編譯執行:

 

C:/go/bin/go.exe run test5.go [E:/project/go/proj/src/test]

2

1

2

1

1

成功: 程序退出程式碼 0.

注意:上面的案例每次執行結果都不一樣。

 
 

 

示例5:使用break終止select語句的執行

 

package main
 
import "fmt"
 
func main() {
    var ch = make(chan int, 1)
 
    ch <- 1
 
    select {
    case <-ch:
        fmt.Println("This case is selected.")
        break //The following statement in this case will not execute.
        fmt.Println("After break statement")
    default:
        fmt.Println("This is the default case.")
    }
 
    fmt.Println("After select statement.")
}
 

 

 

編譯執行:

 

C:/go/bin/go.exe run test15.go [E:/project/go/proj/src/test]

This case is selected.

After select statement.

成功: 程序退出程式碼 0.