在本教程中,您将学习 select 关键字。要理解本教程,您应该对 Golang 中的通道和Goroutine有扎实的知识。
在 Go 编程语言中,`select` 关键字允许您等待多个 Goroutines 的通信操作。Golang 中的 `select` 类似于 `switch` 语句。`select` 语句的语法遵循 `switch case` 模式。`select` 语句中的不同 `case` 会等待每个来自通道的发送或接收操作。
例如,考虑两个通道,假设是通道 A 和通道 B,它们之间进行一些发送和接收操作。它们还需要等待以实现并发和同步,这在 Golang 中通过 `select` 语句支持。

`select` 关键字在 Golang 中定义 `select`。语法以 `select` 关键字开头,后跟花括号,其中包含一些 `case` 语句,再后跟一些表达式。
语法
Select {
Case <- first_channel (send or receive)
//block1
Case <- second _channel (send or receive)
//block2
default:
//block3
}
你可以注意到 `select` 的结构与 `switch case` 类似。所以让我们通过讨论它们的区别来澄清。

| select | switch |
|---|---|
| 在 Golang 中,Select Goroutines 等待多个通信操作。 | 在 Golang 中,switch 是一种表示 if-else 语句的方法。 |
| Select 与通道和 Goroutines 一起使用。 | switch 与具体数据类型一起使用。 |
| 从多个有效选项中随机选择一个 case。 | 在 Switch 中,每个 case 按顺序执行。 |
| 没有 fallthrough 概念 | 需要 fallthrough |
| Select 是非确定性的,因为你无法预测哪个 case 先运行。它随机选择一个 case。 | Switch 是确定性的,即我们可以预测在 if 或 if-else 语句中哪个指令或块会执行。 |
| select 语法 select { Case <- first_channel (发送或接收) //block1 Case <-second_channel (发送或接收) //block2 default: //block3 } | Switch 语法 switch statement; expression { case expression1: //action case expression2: //action default: //action } |
import "fmt"
func main() {
ch := make(chan int, 4)
ch <- 1
ch <- 2
ch <- 3
ch <- 4
close(ch)
switch {
//case <-ch: // invalid case <-ch in switch (mismatched types int and bool)
case <-ch == 1:
fmt.Println("Firstswitch")
fallthrough
case <-ch == 2:
fmt.Println("Secondswitch")
}
第一次运行的输出 1
输出
Firstswitch Secondswitch Second_select receiver 2
第二次运行的输出 2
输出
Firstswitch Secondswitch Second_select receiver 2
`select` 关键字选择一个通道必须准备好执行发送和接收操作的 `case`。当许多通道都准备好执行(即执行发送和接收操作)时,会随机选择一个 `case`。
//PROGRAM USING SELECT KEYWORD
package main
import (
"fmt"
"time"
)
func main() {
A := make(chan string)
B := make(chan string)
go func(){
time.Sleep(2*time.Second)
A <- "Hello "
}()
go func(){
time.Sleep(1*time.Second)
B <- "Haaiii"
}()
select {
case rec1 := <-A:
fmt.Println("I recived from channel A \n ",rec1)
case rec2 := <-B:
fmt.Println("I received from channel B \n ",rec2)
}
}
输出
I received from channel B Haaiii
在这个程序中,我们分别创建了两个通道,通道 A 和通道 B。我们已经在通道教程中讨论了通道的创建。因此,我们将直接进行,不再讨论通道概念
A := make(chan string) // created channel A
B := make(chan string) // created channel b
该程序定义了两个匿名函数(没有名称的函数),它们将由两个Goroutines调用。`select` 关键字用于等待接收端,并等待通道(即通道 A 和通道 B)的接收端,具体取决于哪个通道首先返回或哪个通道首先包含信息,然后执行一些操作。
go func(){ //anonymous functions
time.Sleep(2*time.Second)
A <- "Hello "
}()
go func(){
time.Sleep(1*time.Second)
B <- "Haaiii"
}()
我们已经向通道 A 写入了信息 "hello",类似地,字符串 "Haaiii" 被发送到通道 B。程序中有两个函数,其中 A 等待 2 秒写入第一个,B 等待 1 秒。
`select` 关键字用于等待这两个通道的接收端。在 `select` 语句内部,有 `switch case` 语句,其中变量 `rec1` 分配了等待 A 的接收端,如果从通道 A 接收到一些信息,它就会被打印出来。
通道 B 也以类似的方式执行。上面显示的输出使概念更清晰。
如果 case 语句未准备好操作,`select` 关键字可以阻塞它们。
package main
import "fmt"
func main() {
channel1 := make(chan string) //created channel1
select {
case receive := <-channel1:
fmt.Println(receive)
}
}
输出
fatal error: all goroutines are asleep - deadlock! goroutine 1 [chan receive]: main.main() /tmp/sandbox2985791004/prog.go:8 +0x36 Program exited.
上述 Go 程序创建了一个名为 channel1 的通道。仅使用 select 语句可以从 channel1 执行接收操作。在上述程序中,没有定义 Goroutines,如果没有 Goroutine,它无法执行发送操作。因此,在这个程序中,我们创建了 channel1,并且 select 正在等待从 channel1 接收,但由于缺少 Goroutines,所以无法接收。这种情况会导致死锁,这意味着 select 语句会切换到阻塞状态。
`select` 概念与通道和 Goroutine 紧密相关。Go 程序中的多个 Goroutine 发送数据并尝试通过通道进行通信。为了促进无堵塞通信,Golang 中包含了 `select` 功能。它控制通过通道执行的发送和接收操作,而不会出现任何堵塞。`select` 语句从一个通道并发接收数据,如果它处于就绪状态,则执行该数据。
注意:与通道和 Goroutines 绑定的 Select 语句在 Go 程序中实现同步和并发。
让我们通过上面会话中讨论的当 `select` 阻塞 `Case` 或 `Channel` 的例子来理解
package main
import "fmt"
func main() {
channel1 := make(chan string)
select {
case receive := <-channel1:
fmt.Println(receive)
default: //default statement
fmt.Println("Executes Default")
}
}
输出
Executes Default
注意:与正常的 switch case 类似,如果 select 内部所有 case 均无效,则打印 default 语句。
当使用 select 读取通道时,它一定会在一段时间后做其他事情,而不是一直阻塞在 select 中。下面是一个简单的例子。
package main
import (
"fmt"
"time"
)
func main() {
timeout := make(chan bool, 1)
go func() {
time.Sleep(2 * time.Second)
timeout <- true
}()
ch := make(chan int)
select {
case <-ch:
case <-timeout:
fmt.Println("timeout 01")
case <-time.After(time.Second * 1):
fmt.Println("timeout 02")
}
输出
timeout 01
创建一个超时通道,这样其他地方就可以使用触发超时通道来完成 select 执行,或者还有另一种通过 time.After 机制来编写它的方法。
package main
import (
"fmt"
"time"
)
func main() {
timeout := make(chan bool, 1)
go func() {
time.Sleep(2 * time.Second)
timeout <- true
}()
ch := make(chan int)
select {
case <-ch:
case <-timeout:
fmt.Println("timeout 01")
case <-time.After(time.Second * 1):
fmt.Println("timeout 02")
}
}
输出
timeout 02
让我们看一个示例程序
package main
import (
"fmt"
)
func main() {
ch := make(chan int, 1)
ch <- 1
select {
case ch <- 2:
fmt.Println("channel value is", <-ch)
fmt.Println("channel value is", <-ch)
default:
fmt.Println("channel blocking")
}
}
输出
channel blocking
首先声明缓冲区大小为 1 的通道,然后放入值以填满通道。此时,可以使用 select + default 方法来确保通道是否已满。上面的示例将输出通道阻塞,我们将程序更改为以下内容:
package main
import (
"fmt"
)
func main() {
ch := make(chan int, 2)
ch <- 1
select {
case ch <- 2:
fmt.Println("channel value is", <-ch)
fmt.Println("channel value is", <-ch)
default:
fmt.Println("channel blocking")
}
}
将缓冲区大小更改为 2 后,您可以继续将值插入通道。
输出
channel value is 1 channel value is 2