携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第19天,点击查看活动详情
前言
相信大家对于switch
并不陌生,然而select
跟switch
有个共同特性就是都通过 case
的方式来处理,但是select
跟switch
处理的事情完全不同,也完全不相容,本文带大家认识Go
语言的select
用法,需要的朋友可以参考以下内容,希望对大家有帮助。
定义
select是Go中的一个控制结构,类似于通信的switch语句,每个case必须是一个通信操作,要么是发送要么是接收。
select随机执行一个可运行的case。如果没有case可运行,他将阻塞,直到有case可运行,一个默认的子句总是可运行的。
语法
以下是select语句的语法:
select {
case <-ch1:
// 如果从 ch1 信道成功接收数据,则执行该分支代码
case ch2 <- 1:
// 如果成功向 ch2 信道成功发送数据,则执行该分支代码
default:
// 如果上面都没有成功,则进入 default 分支处理流程
}
复制代码
可以看到select的语法结构有点类似于switch,但又有些不同。
select里的case后面并不带判断条件,而是一个信道的操作,不同于switch里的case。
注意事项:
- select语句只能用于channel信道的IO操作,每个case都必须是一个通信。
- 如果不设置default语句,当没有IO操作发生时,select语句就会一直阻塞,直到某个通信可以运行。
- 如果有一个或多个IO操作时,Go运行会随机选择一个case执行,其他的不会执行,但此时无法保证执行顺序。
- 对于case语句,如果存在信道值为nil的读写操作,则该分支将被忽略。
- 对于空的select语句,会引起死锁。
- 对于在for中的select语句,不能添加default,否则会引起cpu占用过高的问题。
示例
package main
import "fmt"
func main() {
var c1, c2, c3 chan int
var i1, i2 int
select {
case i1 = <-c1:
fmt.Printf("received ", i1, " from c1\n")
case c2 <- i2:
fmt.Printf("sent ", i2, " to c2\n")
case i3, ok := (<-c3): // same as: i3, ok := <-c3
if ok {
fmt.Printf("received ", i3, " from c3\n")
} else {
fmt.Printf("c3 is closed\n")
}
default:
fmt.Printf("no communication\n")
}
}
复制代码
典型
多个IO操作发生时,case语句是随机执行的
switch 里的 case 是顺序执行的,但在 select 里却不是。
func main() {
ch1 := make(chan string, 1) // 创建 一个长度带缓冲的整型通道
ch1 <- "aaaa" // 向通道中写入数据
ch2 := make(chan string, 1)
ch2 <- "bbbb"
//多次执行后,会随机打印 “ch1 read” 或 “ch2 read”
select {
case <-ch1:
fmt.Println("ch1 read")
case <-ch2:
fmt.Println("ch2 read")
}
}
复制代码
空select语句会引发死锁
func main() {
select {
}
}
复制代码
避免造成死锁
如果在遍历完所有的 case 后,若没有可执行的任何一个 case 表达式,就会进入 default 里的代码分支。
如果没有写 default 分支,select 就会阻塞,直到有某个 case 可以命中,而如果一直没有命中,select 就会抛出 deadlock 的错误。
扫描二维码关注公众号,回复:
14483357 查看本文章

解决:
养成好习惯,在 select 的时候,也写好 default 分支代码,尽管你 default 下没有写任何代码。
超时实现
func main() {
quit := make(chan bool)
ch := make(chan int)
go func() {
for {
select {
case num := <-ch:
fmt.Println("num = ", num)
case <-time.After(5 * time.Second):
fmt.Println("超时")
quit <- true
}
}
}()
for i := 0; i < 2; i++ {
ch <- i
time.Sleep(time.Second)
}
<-quit // 等待超时后, 结束 main主线程
fmt.Println("程序结束")
}
复制代码
总结
select 与 switch 原理很相似,但它的使用场景更特殊,学习了本篇文章,你需要知道如下几点区别:
- select 只能用于 channel 的操作(写入/读出),而 switch 则更通用一些;
- select 的 case 是随机的,而 switch 里的 case 是顺序执行;
- select 要注意避免出现死锁,同时也可以自行实现超时机制;
- select 里没有类似 switch 里的 fallthrough 的用法;
- select 不能像 switch 一样接函数或其他表达式;