Go select死锁分析

先看一个例子:

package main


import "sync"

func main() {
	var wg sync.WaitGroup
	foo := make(chan int)
	bar := make(chan int)
	wg.Add(1)
	go func() {
		defer wg.Done()
		select {
		case foo <- <-bar:
		default:
			println("default")
		}
	}()
	wg.Wait()
}

运行结果:

结果并不是执行 default 分支 ,而是死锁了

原因最后分析,接着看下一个例子:

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)
	}
}

运行结果:

 

发现还是死锁,每次都直接输出5个后死锁,同时多运行几次每次输出的5个结果也不相同。

如果将代码中select代码块改为下面这样就一切正常:

select {
case t := <-input1:
  ch <- t
case t := <-input2:
  ch <- t
}
对于 select 语句,在进入该语句时,会按源码的顺序对每一个 case 子句进行求值:这个求值只针对发送或接收操作的额外表达式。

在没有选择某个具体 case 执行前,例子中的 getVal()<-input 和 getch() 会执行。

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
}

执行结果:

 从上面的结果可以看出在没有选择某个具体 case 执行前,,getVal() 都会按照源码顺序执行。

即使将上面的getVal方法加上sleep也不影响结果

func getVal(i int) int {
	fmt.Println("getVal, i=", i)
	time.Sleep(10000000)
	return i
}

结果:

现在回到上面的那个问题 :

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

结果分析:在select随机选择一个case执行前,<-input1 和 <-input2 都会执行,而且是往同一个ch中进行输入,相应的值是: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执行前,<-input1 和 <-input2 都会执行,但是它们都重新申请了各自的变量,将结果存在各自的变量中,并不是对同一个chan输入,所以不会出现丢弃的情况,因为不会出现思索。

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

参考:Go select 竟然死锁了。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值