李林超博客
首页
归档
留言
友链
动态
关于
归档
留言
友链
动态
关于
首页
GO
正文
24.Golang之select
Leefs
2022-07-14 PM
1365℃
0条
[TOC] ### 前言 ![24.Golang之select01.png](https://lilinchao.com/usr/uploads/2022/07/4062750977.png) ### 一、概述 select 是 Golang 中的一个控制结构,语法上类似于switch 语句,只不过select是用于 goroutine 间通信的 ,每个 case 必须是一个通信操作,要么是发送要么是接收,select 会随机执行一个可运行的 case。如果没有 case 可运行,goroutine 将阻塞,直到有 case 可运行。 ### 二、基本语法 **语法** ```go //select基本用法 select { case <- chan1: // 如果chan1成功读到数据,则进行该case处理语句 case chan2 <- 1: // 如果成功向chan2写入数据,则进行该case处理语句 default: // 如果上面都没有成功,则进入default处理流程 } ``` **注意** + 如果没有``default``分支,``select``会阻塞在多个channel上,对多个channel的读/写事件进行监控。 + 如果有一个或多个IO操作可以完成,则Go运行时系统会随机的选择一个执行,否则的话,如果有``default``分支,则执行``default``分支语句,如果连``default``都没有,则``select``语句会一直阻塞,直到至少有一个IO操作可以进行。 ### 三、Select使用 #### 3.1 同时从多个通道中接收数据 ```go import ( "fmt" "time" ) func main() { //1.定义一个通道 10个数据int intChan := make(chan int, 10) for i := 0; i < 10; i++ { intChan <- i } //2.定义一个通道 5个数据string stringChan := make(chan string, 5) for i := 0; i < 5; i++ { stringChan <- "hello" + fmt.Sprintf("%d", i) } //使用select来获取channel里面的数据的时候不需要关闭channel for { select { case v := <-intChan: fmt.Printf("从 intChan 读取的数据%d\n", v) time.Sleep(time.Millisecond * 50) case v := <-stringChan: fmt.Printf("从 stringChan 读取的数据%v\n", v) time.Sleep(time.Millisecond * 50) default: fmt.Printf("数据获取完毕") return //注意退出... } } } ``` **运行结果** ``` 从 stringChan 读取的数据hello0 从 stringChan 读取的数据hello1 从 stringChan 读取的数据hello2 从 intChan 读取的数据0 从 stringChan 读取的数据hello3 从 intChan 读取的数据1 从 stringChan 读取的数据hello4 从 intChan 读取的数据2 从 intChan 读取的数据3 从 intChan 读取的数据4 从 intChan 读取的数据5 从 intChan 读取的数据6 从 intChan 读取的数据7 从 intChan 读取的数据8 从 intChan 读取的数据9 数据获取完毕 ``` #### 3.2 造成死锁 当程序中不使用`default` 分支,如果一直没有命中其中的某个 `case` 最后会造成死锁。 ```go import "fmt" func main() { // 创建三个通道 ch1 := make(chan string, 1) ch2 := make(chan string, 1) ch3 := make(chan string, 1) select { case message1 := <-ch1: // 如果从通道 1 收到数据 fmt.Println("ch1 received:", message1) case message2 := <-ch2: // 如果从通道 2 收到数据 fmt.Println("ch2 received:", message2) case message3 := <-ch3: // 如果从通道 3 收到数据 fmt.Println("ch3 received:", message3) } } ``` **运行报错** ``` fatal error: all goroutines are asleep - deadlock! goroutine 1 [select]: main.main() D:/Codes/gocode/godemo/base11/test02.go:11 +0xfd ``` 运行上面的程序会造成死锁。解决该问题的方法是写好 `default` 分支。 **空 select造成死锁** ```go package main func main() { select {} //fatal error: all goroutines are asleep - deadlock! } ``` 空`select` 语句没有任何 `case`,因此它会一直阻塞,导致死锁。 #### 3.3 select 超时处理 当 `case` 里的通道始终没有接收到数据时,而且也没有 `default` 语句时, `select` 整体就会阻塞,但是有时我们并不希望 `select` 一直阻塞下去,这时候就可以手动设置一个超时时间。 ```go 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) c3 := 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 msg3 := <-c3: fmt.Println("c3 received: ", msg3) case <-timeout: fmt.Println("Timeout, exit.") } } ``` **运行结果** ``` Timeout, exit. ``` ### 总结 1. select语句只能用于信道的读写操作; 2. select中的case条件(非阻塞)是并发执行的,select会选择先操作成功的那个case条件去执行,如果多个同时返回,则随机选择一个执行,此时将无法保证执行顺序。对于阻塞的case语句会直到其中有信道可以操作,如果有多个信道可操作,会随机选择其中一个 case 执行; 3. 对于case条件语句中,如果存在信道值为nil的读写操作,则该分支将被忽略,可以理解为从select语句中删除了这个case语句; 4. 如果有超时条件语句,判断逻辑为如果在这个时间段内一直没有满足条件的case,则执行这个超时case。如果此段时间内出现了可操作的case,则直接执行这个case。一般用超时语句代替了default语句; 5. 对于空的select{},会引起死锁; 6. 对于for中的select{}, 也有可能会引起cpu占用过高的问题。 *附参考文章链接* *http://www.go-edu.cn/2022/05/27/go-16-select/*
标签:
Golang基础
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:
https://lilinchao.com/archives/2252.html
上一篇
23.Golang之Channel示例
下一篇
25.Golang并发安全和锁
评论已关闭
栏目分类
随笔
2
Java
326
大数据
229
工具
31
其它
25
GO
47
NLP
4
标签云
稀疏数组
人工智能
Redis
JVM
Python
查找
NIO
gorm
FastDFS
Golang
VUE
pytorch
机器学习
Sentinel
并发线程
GET和POST
数据结构和算法
Linux
SQL练习题
高并发
Spark SQL
MyBatis
Spark Core
Livy
Quartz
Map
SpringBoot
正则表达式
Jenkins
MyBatis-Plus
友情链接
申请
范明明
庄严博客
Mx
陶小桃Blog
虫洞
评论已关闭