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.