ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Go select 死锁引发的思考

2022-03-02 12:02:10  阅读:158  来源: 互联网

标签:case ch getVal int 死锁 func Go select


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异常

标签:case,ch,getVal,int,死锁,func,Go,select
来源: https://www.cnblogs.com/xiaofua/p/15954405.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有