Go select 死锁引发的思考

https://mp.weixin.qq.com/s/Ov1FvLsLfSaY8GNzfjfMbg一文引发的延续思考

上文总结

总结一

package main

import (
"fmt"
) func main() {
ch := make(chan int)
go func() {
select {
case ch <- getVal(1):
fmt.Println("in first case")
case ch <- getVal(2):
fmt.Println("in second case")
default:
fmt.Println("default")
}
}() fmt.Println("The val:", <-ch)
} func getVal(i int) int {
fmt.Println("getVal, i=", i)
return i
}

无论 select 最终选择了哪个 case,getVal() 都会按照源码顺序执行:getVal(1) 和 getVal(2),也就是它们必然先输出:

getVal, i= 1
getVal, i= 2

总结二

package main

import (
"fmt"
"time"
) func talk(msg string, sleep int) <-chan string {
ch := make(chan string)
go func() {
for i := 0; i < 5; i++ {
ch <- fmt.Sprintf("%s %d", msg, i)
time.Sleep(time.Duration(sleep) * time.Millisecond)
}
}()
return ch
} func fanIn(input1, input2 <-chan string) <-chan string {
ch := make(chan string)
go func() {
for {
select {
case ch <- <-input1:
case ch <- <-input2:
}
}
}()
return ch
} func main() {
ch := fanIn(talk("A", 10), talk("B", 1000))
for i := 0; i < 10; i++ {
fmt.Printf("%q\n", <-ch)
}
}

每次进入以下 select 语句时:

select {
case ch <- <-input1:
case ch <- <-input2:
}

<-input1<-input2 都会执行,相应的值是:A x 和 B x(其中 x 是 0-5)。但每次 select 只会选择其中一个 case 执行,所以 <-input1<-input2 的结果,必然有一个被丢弃了,也就是不会被写入 ch 中。因此,一共只会输出 5 次,另外 5 次结果丢掉了。(你会发现,输出的 5 次结果中,x 比如是 0 1 2 3 4)

而 main 中循环 10 次,只获得 5 次结果,所以输出 5 次后,报死锁。

如果改为这样就一切正常:

select {
case t := <-input1:
ch <- t
case t := <-input2:
ch <- t
}

我的理解:

case ch <- <-input:语句是分成两段执行的,可以理解为

t := <- input //case选择还未明确的时候会执行
ch <- t //如果没有选择此case,则不执行此语句
并且这是两条语句,具有先后顺序
所以<-input 执行后,没有选择此case,<-input的结果就会被丢弃掉,从而导致上述的死锁问题。

问题的引申

上述提到

无论 select 最终选择了哪个 case,getVal() 都会按照源码顺序执行:getVal(1) 和 getVal(2),也就是它们必然先输出:

getVal, i= 1
getVal, i= 2

思考一:如果getVal()方法执行的时间不同,select的运行时长是取决于运行时间长的,还是时间的总和?

func getVal1(i int) int {
time.Sleep(time.Second * 1)
fmt.Println("getVal, i=", i)
return i
}
func getVal2(i int) int {
time.Sleep(time.Second * 2)
fmt.Println("getVal, i=", i)
return i
} func main() {
ch := make(chan int)
go func() {
for {
beginTime := time.Now()
select {
case ch <- getVal1(1):
case ch <- getVal2(2):
default:
fmt.Println("")
}
fmt.Println(time.Since(beginTime))
}
}()
time.Sleep(time.Second * 10)
}

输出的结果

getVal, i= 1
getVal, i= 2
3.0015862s
getVal, i= 1
getVal, i= 2
3.0021938s
getVal, i= 1
getVal, i= 2
3.0019246s

可以看出来,每次select都会按顺序执行case语句,并且select的执行时间为case语句的总和

当然在实际生产中也不会有这种写法

正确的写法:

func main() {
begin := time.Now()
ch := make(chan int)
ch2 := make(chan int, 2)
go func() {
ch2 <- getVal1(1)
}()
go func() {
ch2 <- getVal2(2)
}()
go func() {
for {
select {
case d := <-ch2:
ch <- d
}
}
}()
for i := 0; i < 2; i++ {
fmt.Println(<-ch)
}
fmt.Println(time.Since(begin))
}

输出结果,此时取决于运行时间最长的getVal()

getVal, i= 1
1
getVal, i= 2
2
2.0020979s

在实际生产中,select语句只用于接受channel中的数值,而不是去执行某一方法

细心的小伙伴已经发现了,上述的写法有两个bug

  1. 新起协程中,因为for语句导致一直空转,该协程不会被销毁
  2. 如果ch被close以后,对其发送数据,会导致panic

加点注释看看输出的结果

func main() {
begin := time.Now()
ch := make(chan int)
ch2 := make(chan int, 2)
go func() {
ch2 <- getVal1(1)
}()
go func() {
ch2 <- getVal2(2)
}()
time.Sleep(2 * time.Second)
fmt.Println("goroutine num", runtime.NumGoroutine())
go func() {
defer func() {
if r := recover(); r != nil {
fmt.Println("panic err", r)
}
}()
for {
select {
case d := <-ch2:
ch <- d
}
}
}()
for i := 0; i < 2; i++ {
fmt.Println(<-ch)
}
close(ch)
fmt.Println(time.Since(begin))
fmt.Println("goroutine num", runtime.NumGoroutine())
ch2 <- 1
time.Sleep(time.Second * 1)
}

输出的结果

getVal, i= 1
getVal, i= 2
goroutine num 2
1
2
2.0020965s
goroutine num 2
panic err send on closed channel

可以看到,for循环的协程并没有被释放,并且在后续的ch <-操作中也报出了panic异常

Go select 死锁引发的思考的更多相关文章

  1. 曲演杂坛--一条DELETE引发的思考

    原文:曲演杂坛--一条DELETE引发的思考 场景介绍: 我们有一张表,专门用来生成自增ID供业务使用,表结构如下: CREATE TABLE TB001 ( ID ,) PRIMARY KEY, D ...

  2. Spring之LoadTimeWeaver——一个需求引发的思考---转

    原文地址:http://www.myexception.cn/software-architecture-design/602651.html Spring之LoadTimeWeaver——一个需求引 ...

  3. 由SecureCRT引发的思考和学习

    由SecureCRT引发的思考和学习 http://mp.weixin.qq.com/s?__biz=MzAxOTAzMDEwMA==&mid=2652500597&idx=1& ...

  4. 解决一道leetcode算法题的曲折过程及引发的思考

    写在前面 本题实际解题过程是 从 40秒 --> 24秒 -->1.5秒 --> 715ms --> 320ms --> 48ms --> 36ms --> ...

  5. 【思考】由安装zabbix至排障php一系列引发的思考

    [思考]由安装zabbix至排障php一系列引发的思考 linux的知识点林立众多,很有可能你在排查一个故障的时候就得用到另一门技术的知识: 由于linux本身的应用依赖的库和其它环境环环相扣,但又没 ...

  6. 由<a href = "#" > 引发的思考

    原文:由<a href = "#" > 引发的思考 前阵子在一个移动项目中,通过 <a href = "#" >  的方式 绑定clic ...

  7. class_copyIvarList方法获取实例变量问题引发的思考

    在runtime.h中,你可以通过其中的一个方法来获取实例变量,那就是class_copyIvarList方法,具体的实现如下: - (NSArray *)ivarArray:(Class)cls { ...

  8. 由一个emoji引发的思考

    由一个emoji引发的思考 从毕业以来,基本就一直在做移动端,但是一直就关于移动端的开发,各种适配问题的解决,在日常搬砖中处理了就过了,也没有把东西都沉淀下来,觉得甚是寒颜.现就一个小bug,让我们来 ...

  9. 一次composer错误使用引发的思考

    一次composer错误使用引发的思考 这个思考源自于一个事故.让我对版本依赖重新思考了一下. 事故现象 一个线上的管理后台,一个使用laravel搭建的管理后台,之前在线上跑的好好的,今天comop ...

随机推荐

  1. 在ajax请求中,contentType 和 dataType 的区别?

    一.在ajax请求中,contentType 和 dataType 的区别? 1.contentType 内容类型. 1.1默认是 "application/x-www-form-urlen ...

  2. 【以太坊剖析】以太坊虚拟机(EVM)之基本定义

    以太坊虚拟机(EVM) 以太坊虚拟机(Ethereum Virtual Machine,简称EVM)是一个基于栈的虚拟机,基于特定的环境数据,执行一系列的字节代码形式的指令,以修改系统状态.EVM目前 ...

  3. libcurl库openssl编译

    openssl编译 win32: perl Configure  no-shared VC-WIN32 --prefix=E:/lib/openssl-1.1.1 linux: ./config -f ...

  4. Cell简介

    UITableView的每一行都是一个UITableViewCell,通过dataSource的tableView:cellForRowAtIndexPath:方法来初始化每一行 UITableVie ...

  5. mybatis的一对多(collection)

    使用图解: 低效率查询: 高效率查询: 1 查询用联合查询 2<collection 里面不写column

  6. Solution -「NOI 模拟赛」彩色挂饰

    \(\mathcal{Description}\)   给定一个含 \(n\) 个点 \(m\) 条边的简单无向图,设图中最大点双的大小为 \(s\),则保证 \(s\le6\).你将要用 \(k\) ...

  7. MYSQL数据表损坏的原因分析和修复方法小结

    MYSQL数据表损坏的原因分析和修复方法小结 1.表损坏的原因分析 以下原因是导致mysql 表毁坏的常见原因: 1. 服务器突然断电导致数据文件损坏. 2. 强制关机,没有先关闭mysql 服务. ...

  8. 注意!你的 Navicat 可能被下毒了...

    大家早上好,我是程序猿DD! 刚刚看到一份来自微步在线发布的威胁情报通报,其中提到了被我们广泛应用的数据库管理工具Navicat Premium被投毒消息!如果你有用过相关版本的话,可能当前正处于数据 ...

  9. python.36的特性新定义初学者必看课程

    一.Python3.6新特性 1.新的格局化字符串办法 <p "="">新的格局化字符串办法,即在一般字符串前增加 f 或 F 前缀,其效果相似于str.fo ...

  10. C# 实例解释面向对象编程中的里氏替换原则

    在面向对象编程中,SOLID 是五个设计原则的首字母缩写,旨在使软件设计更易于理解.灵活和可维护.这些原则是由美国软件工程师和讲师罗伯特·C·马丁(Robert Cecil Martin)提出的许多原 ...