大佬教程收集整理的这篇文章主要介绍了Go select 死锁引发的思考,大佬教程大佬觉得挺不错的,现在分享给大家,也给大家做个参考。
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
加点注释看看输出的结果
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 死锁引发的思考全部内容,希望文章能够帮你解决Go select 死锁引发的思考所遇到的程序开发问题。
如果觉得大佬教程网站内容还不错,欢迎将大佬教程推荐给程序员好友。
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。