Go 知识点(09)— for select 作用于 channel
1. for select 作用于未关闭的通道
1.1 没有 default 分之场景
先看下面代码
func main() {ch := make(chan int, 3)go func() {time.Sleep(2 * time.Second) // 延迟往通道里里面发送数据ch <- 1}()for {select {case v, ok := <-ch:fmt.Printf("v=%v, ok=%v\n", v, ok)time.Sleep(1 * time.Second)}fmt.Println("waiting")}
}
执行代码输出结果如下:
v=1, ok=true
waiting
fatal error: all goroutines are asleep - deadlock!goroutine 1 [chan receive]:
main.main()/home/wohu/project/go/src/demo/demo.go:17 +0x82
exit status 2
从结果我们可以看到:
- 当通道中没有数据时,且
select
语句没有default
分之时,会一直阻塞在case
语句中; - 当通道中有数据时,
case
语句拿到通道里面的值之后,继续执行select
语句块之外的其余for
循环体语句; - 当通道里面的数据被取走之后,
case
语句一直等待从通道中取数据,但是一直没有数据发送过来就会造成死锁;
怎么避免这个死锁问题呢? 这就需要加上 default
分之了。
1.2 有 default 分之场景
查看下面代码
func main() {ch := make(chan int, 3)go func() {time.Sleep(2 * time.Second) // 延迟往通道里里面发送数据ch <- 1}()for {select {case v, ok := <-ch:fmt.Printf("v=%v, ok=%v\n", v, ok)time.Sleep(1 * time.Second)default:fmt.Println("通道没有数据")time.Sleep(1 * time.Second)}fmt.Println("waiting")}
}
运行代码输出结果如下:
通道没有数据
waiting
通道没有数据
waiting
v=1, ok=true
waiting
通道没有数据
waiting
通道没有数据
从结果我们可以看到:
- 当通道中没有数据时,走
default
分之,default
分之完成后会继续执行for
循环体的其它语句; - 当通道中有数据,则会执行对应的
case
分之; - 当通道再一次没有数据时,则继续会执行
default
分之和剩余的其它for
循环体语句,而且会一直死循环执行;
2. for select 作用于关闭的通道
2.1 对关闭的通道执行 case 会造成死循环
继续下面代码
func main() {ch := make(chan int, 3)go func() {time.Sleep(2 * time.Second) // 延迟往通道里里面发送数据ch <- 1close(ch)}()for {select {case v, ok := <-ch:fmt.Printf("v=%v, ok=%v\n", v, ok)time.Sleep(1 * time.Second)default:fmt.Println("通道没有数据")time.Sleep(1 * time.Second)}fmt.Println("waiting")}
}
运行输出结果
通道没有数据
waiting
通道没有数据
waiting
v=1, ok=true
waiting
v=0, ok=false
waiting
v=0, ok=false
...
注意我们在前面已经将通道关闭,这个时候的 case
语句依然成立,所以会形成死循环执行这个 case
语句。
那么怎样能跳出这个死循环的 case
语句呢?
2.2 跳出死循环的 case 语句
要跳出这个死循环的 case
语句,我们需要在 case
中通过第二个参数判断 chan
是否关闭,如果关闭则通过 make(chan type)
来将关闭的 chan
置 nil
,当再次执行到 select
时,因为 chan
是 nil
会进入阻塞。
而 select
中如果任意某个分之可读(包括 default
),它就会被执行,其他被忽略。所以在有 default
分之场景时, select
会跳过这个阻塞 case
,去执行 default
分之,这样就可以避开这个死循环的 case
分之。
func main() {ch := make(chan int, 3)go func() {time.Sleep(2 * time.Second) // 延迟往通道里里面发送数据ch <- 1close(ch)}()for {select {case v, ok := <-ch:if !ok {ch = make(chan int)fmt.Println("通道已经关闭")} else {fmt.Printf("v=%v, ok=%v\n", v, ok)time.Sleep(1 * time.Second)}default:fmt.Println("通道没有数据")time.Sleep(1 * time.Second)}fmt.Println("waiting")}
}
输出结果如下:
通道没有数据
waiting
通道没有数据
waiting
v=1, ok=true
waiting
通道已经关闭
waiting
通道没有数据
waiting
...
会一直循环打印 default
分之的输出,那怎样跳出这个循环呢?
2.3 跳出 for select 循环语句
- 可以使用
goto
加lable
跳转到for
外面; - 可以设置一个额外的标记位,当
chan
关闭时,设置flag=true
,在for
的最后判断flag
决定是否break
;
我们采用第二种方案:
func main() {ch := make(chan int, 3)go func() {time.Sleep(2 * time.Second) // 延迟往通道里里面发送数据ch <- 1close(ch)}()exitFlag := falsefor {select {case v, ok := <-ch:if !ok {ch = make(chan int)fmt.Println("通道已经关闭")exitFlag = true} else {fmt.Printf("v=%v, ok=%v\n", v, ok)time.Sleep(1 * time.Second)}default:fmt.Println("通道没有数据")time.Sleep(1 * time.Second)}if exitFlag {fmt.Println("跳出循环")break}fmt.Println("waiting")}
}
输出结果
通道没有数据
waiting
通道没有数据
waiting
v=1, ok=true
waiting
通道已经关闭
跳出循环
由以上示例我们可以得出以下结论:
select
语句中如果任意某个case
的通道有值可读时,它就会被执行,其他case
会被忽略;- 如果没有
default
语句,select
将有可能阻塞,直到某个case
分之有值可以运行,所以select
里最好有一个default
,否则将有一直阻塞的风险;
如果 select
语句发现同时有多个候选分支满足选择条件,那么它就会用一种伪随机的算法在这些分支中选择一个并执行。
仅当 select
语句中的所有 case
表达式都被求值完毕后,它才会开始选择候选分支。这时候,它只会挑选满足选择条件的候选分支执行。如果所有的候选分支都不满足选择条件,那么默认分支就会被执行。如果这时没有默认分支,那么 select
语句就会立即进入阻塞状态,直到至少有一个候选分支满足选择条件为止。一旦有一个候选分支满足选择条件,select
语句就会被唤醒,这个候选分支就会被执行。
Go 知识点(09)— for select 作用于 channel相关推荐
- 关于拉普拉斯算子作用于(1/r)的证明
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言 一.算子结果 二.证明 引用 前言 在计算氢原子的能级时,会用到拉普拉斯算子作用于(1/r),r是球坐标下的径向坐标, ...
- svg文件引入及如何作用于vue
svg文件引入及如何作用于vue ** 直接引入 将svg代码直接嵌入在vue内部,可以直接进行相关事件绑定操作 文件引入 关于svg文件引入,首先最重要的一部分就是svg文件引入使用的话,需要在服务 ...
- 计算机刷帖知识点,09计算机408分(沙滩帖)
又是一轮考生启航,长江后浪推前浪,作为09考生,无论成功与否,无论去向何方,我们都已经登岸.有句话叫长江后浪推前浪,顾明沙滩贴.回头看那过去的时间,已经分辨不出是什么感觉.只能把一些心得,想留在这片阵 ...
- Golang的select多路复用以及channel使用实践
看到有个例子实现了一个类似于核弹发射装置,在发射之前还是需要随时能输入终止发射. 这里就可以用到cahnnel 配合select 实现多路复用. select的写法用法有点像switch.但是和swi ...
- 自考计算机原理知识点,09年自考计算机网络实用技术知识点:ATM原理
1.AIM的定义与功能 CCITr在I系列建议中给ATM下了这样的定义:ATM是一种转换模式(即前面所说的传输方式),在这一模式中信息被组织成信元(Cell),包含一段信息的信元并不需要周期性地出现在 ...
- 作用于HTML元素的Vue.js指令
我在这里学习内置指令 v-model v-if v-show v-else v-for v-bind v-on v-model表单数据模型双向绑定 example: ① 表单文本输入框效果 <i ...
- MySQL 09 DQL → select 初识查询数据和别名的使用
4.1 DQL → select 查询数据和别名的使用 DQL:Data Query LANGUAGE(数据查询语言) 所有的查询操作都用它 select 简单的查询,无论多么复杂的查询它 都可以做到 ...
- 考研计算机统考知识点,09计算机考研统考大纲权威解读之考试范围
09年计算机考研统考大纲权威解读之考试范围分析 下面我们来说说新大纲的考查范围,统考大纲的考查范围说的比较笼统,考查的知识点方面也只是把有关教材的章节名列举出来,并没有强调每个知识点应该掌握到什么程度 ...
- Go 知识点(03)— 非缓冲 channel 的长度始终为 0
我们先看下面代码输出通道的长度是多少? func main() {ch := make(chan string)go func() {ch <- "hello"close(c ...
最新文章
- 电平转换电路(三极管共射极)
- Github-emoji表情图像大全
- 使用DataAnnotations实现数据验证
- 三、VueJs 填坑日记之项目文件认识
- 米哈游web前端面试题Js/Vue/浏览器原理等
- RFID(Radio Frequency Identification)技术,又称无线射频识别
- 大型系统集成项目流程方案设计图
- TextView列表页面跳转的简洁写法.
- Servlet【黑马】
- java宠物商店管理系统_Java实现宠物商店管理系统
- 高德地图猎鹰sdk服务service Id的创建
- 《小强升职记》作者谈“怎样达成目标”
- Cant connect to MySQL server on 192.168.164.130
- Java常见算法(五)【二叉树:morris遍历】
- 展示一个一键分享插件
- NetworkManager——linux强大的网络管理工具
- Navicat首次连接MySQL8.0出现错误#1045 - Access denied for user ‘root‘@‘localhost‘(using password: YES)的解决办法
- 【PHP】创蓝253云通讯paas平台短信验证码接口调用emo
- 3、DQL(数据查询语句)
- CCSP 云安全认证-法律与合规风险