1、什么是select语句?
select 是 Go 中的一个控制结构。select 语句类似于 switch 语句,但是select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。
2、select语句的作用?
当我们需要同时从多个通道接收数据时,如果通道里没有数据将会发生阻塞。为了应对这种场景,Go内置了select关键字,可以同时响应多个通道的操作。
3、select语句的语法结构
select {
case 表达式1:
statement(s);
case 表达式2:
statement(s);
default : /* 可选 */
statement(s);
}
注意:它仅能用于channel的相关操作,select 里的 case 表达式要求是对信道的操作
示例:
package main
import (
"fmt"
)
func main() {
c1 := make(chan string, 1)
c2 := make(chan string, 1)
c2 <- "hello"
select {
case msg1 := <-c1:
fmt.Println("c1 received: ", msg1)
case msg2 := <-c2:
fmt.Println("c2 received: ", msg2)
default:
fmt.Println("No data received.")
}
}
输出:
c2 received: hello
select语句的处理逻辑:case随机选择处理列出的多个通信情况中的一个
- 如果都阻塞了,会等待直到其中一个可以处理
- 如果多个可以处理,随机选择一个
- 为了避免死锁,应该编写default分支或手动实现超时机制,否则select 整体就会一直阻塞
超时示例:
package main
import (
"fmt"
"time"
)
func main() {
channel1 := make(chan int)
channel2 := make(chan int)
select {
case <-channel1:
fmt.Println("=====channel1=====")
case <-channel2:
fmt.Println("=====channel1=====")
case <-time.After(3 * time.Second):
fmt.Println("====timeout====")
//or
//default:
// fmt.Println("====default====")
}
}
输出:
====timeout====
4、select与switch的区别
- select 的 case 是随机的,而 switch 里的 case 是顺序执行;
- select 里没有类似 switch 里的 fallthrough 的用法;
- select 里的 case 表达式要求是对信道的操作,不能像 switch 一样接函数或其他表达式;
5、select的应用场景
- 生产者消费者模式
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
go produce1(ch1)
go produce2(ch2)
go consume(ch1, ch2)
time.Sleep(1e9)
}
func produce1(ch chan int) {
for i := 0; ; i++ {
ch <- i * 2
}
}
func produce2(ch chan int) {
for i := 0; ; i++ {
ch <- i + 5
}
}
func consume(ch1, ch2 chan int) {
for {
select {
case v := <-ch1:
fmt.Printf("data from channel 1: %d\n", v)
case v := <-ch2:
fmt.Printf("data from channel 2: %d\n", v)
}
}
}
输出(随机交替打印知道main程序到时退出):
...
data from channel 1: 82550
data from channel 1: 82552
data from channel 1: 82554
data from channel 1: 82556
data from channel 1: 82558
data from channel 1: 82560
data from channel 1: 82562
data from channel 1: 82564
data from channel 1: 82566
data from channel 1: 82568
data from channel 1: 82570
data from channel 1: 82572
...
- 用于判断管道是否存满
package main
import (
"fmt"
"time"
)
func main() {
output1 := make(chan string, 10)
go write(output1)
for s := range output1 {
fmt.Println("res:", s)
time.Sleep(3*time.Second)
}
}
func write(ch chan string) {
for {
select {
// 写数据
case ch <- "hello":
fmt.Println("write hello")
default:
fmt.Println("channel full")
}
time.Sleep(time.Millisecond * 500)
}
}
输出:
write hello
res: hello
write hello
write hello
write hello
write hello
write hello
res: hello
write hello
write hello
write hello
write hello
write hello
write hello
res: hello
write hello
channel full
channel full
channel full
channel full
channel full
res: hello
write hello
channel full
...
本文参考了
The way to go-使用select切换协程
Go时光编程-理解select用法
ToGopher-select多路复用