今天深入了解下Go语言中select的用法,和switch case很类似,用法比较单一,它仅能用于 信道/通道 的相关操作,每个case会对应一个通道的通信(接收或发送)过程。select会一直等待,直到某个case的通信操作完成时,就会执行case分支对应的语句
go
代码解读
复制代码
//语法如下:
select{
case <-ch1:
...
case data := <-ch2:
...
case ch3<-data:
...
default:
默认操作
}
接下来,我们来看几个例子帮助理解这个 select 的模型。
1、最简单的例子
go
代码解读
复制代码
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.")
}
}
在运行 select 时,会遍历所有)的 case 表达式,只要有一个信道有接收到数据,那么 select 就结束,所以输出如下c2 received: hello
,c2接收到数据了
2. 避免造成死锁
select
在执行过程中,必须命中其中的某一分支。如果在遍历完所有的 case 后,若没有命中任何一个 case 表达式,就会进入 default 里的代码分支。
但刚好这时你没有写 default
分支,会发生什么呢?这时select 就会阻塞
,直到有某个 case 可以命中,而如果一直没有命中,select 就会抛出 deadlock
的错误,就像下面这样子。
go
代码解读
复制代码
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.")
}
}
运行后输出如下
go
代码解读
复制代码
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [select]:
nginxlog/tools.OutPrintf()
D:/work/perlearn/go_space/total/tools/parasenginx.go:233 +0xc5
main.main()
D:/work/perlearn/go_space/total/main.go:8 +0x17
Process finished with exit code 2
3、解决这个问题的方法有两种
一个是,养成好习惯,在 select 的时候,也写好 default 分支代码,尽管你 default 下没有写任何代码。这时再执行上面代码就不会deadlock
go
代码解读
复制代码
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:
}
}
另一个是,确保其中某一个信道可以接收到数据
go
代码解读
复制代码
package main
import (
"fmt"
"time"
)
func main() {
c1 := make(chan string, 1)
c2 := make(chan string, 1)
// 开启一个协程,可以发送数据到信道
go func() {
time.Sleep(time.Second * 1)
c2 <- "hello"
}()
select {
case msg1 := <-c1:
fmt.Println("c1 received: ", msg1)
case msg2 := <-c2:
fmt.Println("c2 received: ", msg2)
}
}
4、select 随机性
之前学过 switch 的时候,知道了 switch 里的 case 是顺序执行的,但在 select 里却不是。
go
代码解读
复制代码
func main(){
c1 := make(chan string, 1)
c2 := make(chan string, 1)
c1 <- "hello"
c2 <- "123123"
select {
case msg1 := <-c1:
fmt.Println("c1 received: ", msg1)
case msg2 := <-c2:
fmt.Println("c2 received: ", msg2)
}
}
5. select 的超时
当 case 里的信道始终没有接收到数据时,而且也没有 default 语句时,select 整体就会阻塞,但是有时我们并不希望 select 一直阻塞下去,这时候就可以手动设置一个超时时间
go
代码解读
复制代码
func main(){
ch1:=make(chan string,1)
ch2:=make(chan string,1)
timeout:=make(chan bool,1)
go func(ch chan bool, t int) {
time.Sleep(time.Second * time.Duration(t))
ch <- true
}(timeout,2)
select {
case msg:=<-ch1:
fmt.Println("ch1 recvied data",msg)
case ms2:=<-ch2:
fmt.Println("ch2 recvied data",ms2)
case <-timeout:
fmt.Println("Timeout, exit.")
}
}
输出如下
text
代码解读
复制代码
Timeout, exit.
6、 总结一下
select 与 switch 原理很相似,但它的使用场景更特殊,学习了本篇文章,你需要知道如下几点区别:
- select 只能用于 channel 的操作(写入/读出),而 switch 则更通用一些;
- select 的 case 是随机的,而 switch 里的 case 是顺序执行;
- select 要注意避免出现死锁,同时也可以自行实现超时机制;
- select 里没有类似 switch 里的 fallthrough 的用法;
- select 不能像 switch 一样接函数或其他表达式。
转载来源:https://juejin.cn/post/7047778400404144159