29. Go 语言中的 select 用法

Hi,大家好,我是明哥。

在自己学习 Golang 的这段时间里,我写了详细的学习笔记放在我的个人微信公众号 《Go编程时光》,对于 Go 语言,我也算是个初学者,因此写的东西应该会比较适合刚接触的同学,如果你也是刚学习 Go 语言,不防关注一下,一起学习,一起成长。

我的在线博客:http://golang.iswbm.com
我的 Github:github.com/iswbm/GolangCodingTime


前面写过两节关于 switch-case 的文章,分别是:

流程控制:switch-case

Go 语言中的类型断言

今天要学习一个跟 switch-case 很像,但还有点个人特色select-case,这一节本应该放在 学习 Go 协程:详解信道/通道 里一起讲的,但是当时漏了,直到有读者给我提出,才注意到,今天就用这篇文章补充一下。

跟 switch-case 相比,select-case 用法比较单一,它仅能用于 信道/通道 的相关操作。

Copy
Copy
select { case 表达式1: <code> case 表达式2: <code> default: <code> }

接下来,我们来看几个例子帮助理解这个 select 的模型。

1. 最简单的例子#

先创建两个信道,并在 select 前往 c2 发送数据

Copy
Copy
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 就结束,所以输出如下

Copy
Copy
c2 received: hello

2. 避免造成死锁#

select 在执行过程中,必须命中其中的某一分支。

如果在遍历完所有的 case 后,若没有命中(命中:也许这样描述不太准确,我本意是想说可以执行信道的操作语句)任何一个 case 表达式,就会进入 default 里的代码分支。

但如果你没有写 default 分支,select 就会阻塞,直到有某个 case 可以命中,而如果一直没有命中,select 就会抛出 deadlock 的错误,就像下面这样子。

Copy
Copy
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.") } }

运行后输出如下

Copy
Copy
fatal error: all goroutines are asleep - deadlock! goroutine 1 [select]: main.main() /Users/MING/GolandProjects/golang-test/main.go:13 +0x10f exit status 2

解决这个问题的方法有两种

一个是,养成好习惯,在 select 的时候,也写好 default 分支代码,尽管你 default 下没有写任何代码。

Copy
Copy
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: } }

另一个是,让其中某一个信道可以接收到数据

Copy
Copy
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) } }

3. select 随机性#

之前学过 switch 的时候,知道了 switch 里的 case 是顺序执行的,但在 select 里却不是。

通过下面这个例子的执行结果就可以看出

4. select 的超时#

当 case 里的信道始终没有接收到数据时,而且也没有 default 语句时,select 整体就会阻塞,但是有时我们并不希望 select 一直阻塞下去,这时候就可以手动设置一个超时时间。

Copy
Copy
package main import ( "fmt" "time" ) func makeTimeout(ch chan bool, t int) { time.Sleep(time.Second * time.Duration(t)) ch <- true } func main() { c1 := make(chan string, 1) c2 := make(chan string, 1) timeout := make(chan bool, 1) go makeTimeout(timeout, 2) select { case msg1 := <-c1: fmt.Println("c1 received: ", msg1) case msg2 := <-c2: fmt.Println("c2 received: ", msg2) case <-timeout: fmt.Println("Timeout, exit.") } }

输出如下

Copy
Copy
Timeout, exit.

5. 读取/写入都可以#

上面例子里的 case,好像都只从信道中读取数据,但实际上,select 里的 case 表达式只要求你是对信道的操作即可,不管你是往信道写入数据,还是从信道读出数据。

Copy
Copy
package main import ( "fmt" ) func main() { c1 := make(chan int, 2) c1 <- 2 select { case c1 <- 4: fmt.Println("c1 received: ", <-c1) fmt.Println("c1 received: ", <-c1) default: fmt.Println("channel blocking") } }

输出如下

Copy
Copy
c1 received: 2 c1 received: 4

6. 总结一下#

select 与 switch 原理很相似,但它的使用场景更特殊,学习了本篇文章,你需要知道如下几点区别:

  1. select 只能用于 channel 的操作(写入/读出),而 switch 则更通用一些;
  2. select 的 case 是随机的,而 switch 里的 case 是顺序执行;
  3. select 要注意避免出现死锁,同时也可以自行实现超时机制;
  4. select 里没有类似 switch 里的 fallthrough 的用法;
  5. select 不能像 switch 一样接函数或其他表达式。

系列导读

01. 开发环境的搭建(Goland & VS Code)

02. 学习五种变量创建的方法

03. 详解数据类型:****整形与浮点型

04. 详解数据类型:byte、rune与string

05. 详解数据类型:数组与切片

06. 详解数据类型:字典与布尔类型

07. 详解数据类型:指针

08. 面向对象编程:结构体与继承

09. 一篇文章理解 Go 里的函数

10. Go语言流程控制:if-else 条件语句

11. Go语言流程控制:switch-case 选择语句

12. Go语言流程控制:for 循环语句

13. Go语言流程控制:goto 无条件跳转

14. Go语言流程控制:defer 延迟调用

15. 面向对象编程:接口与多态

16. 关键字:make 和 new 的区别?

17. 一篇文章理解 Go 里的语句块与作用域

18. 学习 Go 协程:goroutine

19. 学习 Go 协程:详解信道/通道

20. 几个信道死锁经典错误案例详解

21. 学习 Go 协程:WaitGroup

22. 学习 Go 协程:互斥锁和读写锁

23. Go 里的异常处理:panic 和 recover

24. 超详细解读 Go Modules 前世今生及入门使用

25. Go 语言中关于包导入必学的 8 个知识点

26. 如何开源自己写的模块给别人用?

27. 说说 Go 语言中的类型断言?

28. 这五点带你理解Go语言的select用法


posted @   王一白  阅读(1610)  评论(0编辑  收藏  举报
编辑推荐:
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
· DeepSeek 解答了困扰我五年的技术问题
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 用 C# 插值字符串处理器写一个 sscanf
阅读排行:
· [翻译] 为什么 Tracebit 用 C# 开发
· 腾讯ima接入deepseek-r1,借用别人脑子用用成真了~
· Deepseek官网太卡,教你白嫖阿里云的Deepseek-R1满血版
· DeepSeek崛起:程序员“饭碗”被抢,还是职业进化新起点?
· RFID实践——.NET IoT程序读取高频RFID卡/标签
历史上的今天:
2017-06-07 windows下安装mysql驱动mysql-python
2017-06-07 CentOS安装Python
2017-06-07 vim快捷键汇总
点击右上角即可分享
微信分享提示

目录

目录

×
+

"一个专注分享Python知识的地方"

微信支付