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) 来将关闭的 channil ,当再次执行到 select 时,因为 channil 会进入阻塞。

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 循环语句

  • 可以使用 gotolable 跳转到 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
通道已经关闭
跳出循环

由以上示例我们可以得出以下结论:

  1. select 语句中如果任意某个 case 的通道有值可读时,它就会被执行,其他 case 会被忽略;
  2. 如果没有 default 语句,select 将有可能阻塞,直到某个 case 分之有值可以运行,所以 select 里最好有一个 default ,否则将有一直阻塞的风险;

如果 select 语句发现同时有多个候选分支满足选择条件,那么它就会用一种伪随机的算法在这些分支中选择一个并执行。

仅当 select 语句中的所有 case 表达式都被求值完毕后,它才会开始选择候选分支。这时候,它只会挑选满足选择条件的候选分支执行。如果所有的候选分支都不满足选择条件,那么默认分支就会被执行。如果这时没有默认分支,那么 select 语句就会立即进入阻塞状态,直到至少有一个候选分支满足选择条件为止。一旦有一个候选分支满足选择条件,select 语句就会被唤醒,这个候选分支就会被执行。

Go 知识点(09)— for select 作用于 channel相关推荐

  1. 关于拉普拉斯算子作用于(1/r)的证明

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言 一.算子结果 二.证明 引用 前言 在计算氢原子的能级时,会用到拉普拉斯算子作用于(1/r),r是球坐标下的径向坐标, ...

  2. svg文件引入及如何作用于vue

    svg文件引入及如何作用于vue ** 直接引入 将svg代码直接嵌入在vue内部,可以直接进行相关事件绑定操作 文件引入 关于svg文件引入,首先最重要的一部分就是svg文件引入使用的话,需要在服务 ...

  3. 计算机刷帖知识点,09计算机408分(沙滩帖)

    又是一轮考生启航,长江后浪推前浪,作为09考生,无论成功与否,无论去向何方,我们都已经登岸.有句话叫长江后浪推前浪,顾明沙滩贴.回头看那过去的时间,已经分辨不出是什么感觉.只能把一些心得,想留在这片阵 ...

  4. Golang的select多路复用以及channel使用实践

    看到有个例子实现了一个类似于核弹发射装置,在发射之前还是需要随时能输入终止发射. 这里就可以用到cahnnel 配合select 实现多路复用. select的写法用法有点像switch.但是和swi ...

  5. 自考计算机原理知识点,09年自考计算机网络实用技术知识点:ATM原理

    1.AIM的定义与功能 CCITr在I系列建议中给ATM下了这样的定义:ATM是一种转换模式(即前面所说的传输方式),在这一模式中信息被组织成信元(Cell),包含一段信息的信元并不需要周期性地出现在 ...

  6. 作用于HTML元素的Vue.js指令

    我在这里学习内置指令 v-model v-if v-show v-else v-for v-bind v-on v-model表单数据模型双向绑定 example: ① 表单文本输入框效果 <i ...

  7. MySQL 09 DQL → select 初识查询数据和别名的使用

    4.1 DQL → select 查询数据和别名的使用 DQL:Data Query LANGUAGE(数据查询语言) 所有的查询操作都用它 select 简单的查询,无论多么复杂的查询它 都可以做到 ...

  8. 考研计算机统考知识点,09计算机考研统考大纲权威解读之考试范围

    09年计算机考研统考大纲权威解读之考试范围分析 下面我们来说说新大纲的考查范围,统考大纲的考查范围说的比较笼统,考查的知识点方面也只是把有关教材的章节名列举出来,并没有强调每个知识点应该掌握到什么程度 ...

  9. Go 知识点(03)— 非缓冲 channel 的长度始终为 0

    我们先看下面代码输出通道的长度是多少? func main() {ch := make(chan string)go func() {ch <- "hello"close(c ...

最新文章

  1. 电平转换电路(三极管共射极)
  2. Github-emoji表情图像大全
  3. 使用DataAnnotations实现数据验证
  4. 三、VueJs 填坑日记之项目文件认识
  5. 米哈游web前端面试题Js/Vue/浏览器原理等
  6. RFID(Radio Frequency Identification)技术,又称无线射频识别
  7. 大型系统集成项目流程方案设计图
  8. TextView列表页面跳转的简洁写法.
  9. Servlet【黑马】
  10. java宠物商店管理系统_Java实现宠物商店管理系统
  11. 高德地图猎鹰sdk服务service Id的创建
  12. 《小强升职记》作者谈“怎样达成目标”
  13. Cant connect to MySQL server on 192.168.164.130
  14. Java常见算法(五)【二叉树:morris遍历】
  15. 展示一个一键分享插件
  16. NetworkManager——linux强大的网络管理工具
  17. Navicat首次连接MySQL8.0出现错误#1045 - Access denied for user ‘root‘@‘localhost‘(using password: YES)的解决办法
  18. 【PHP】创蓝253云通讯paas平台短信验证码接口调用emo
  19. 3、DQL(数据查询语句)
  20. CCSP 云安全认证-法律与合规风险

热门文章

  1. 不同编程语言之间的具体实现对比
  2. python 实现桶排序
  3. 2018半年总结:走过的路
  4. 获取当前脚本目录路径问题汇总
  5. 什么是高/低方差、高/低偏差、(推荐阅读)
  6. python sys.argv是什么?
  7. HMM(隐马尔科夫)用于中文分词
  8. 深度学总结:skip-gram pytorch实现
  9. AI 芯片的分类及技术
  10. 2020年人工智能汽车将出台多项标准