Go select死锁分析

Posted liuhmmjj

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 竟然死锁了。。。

以上是关于Go select死锁分析的主要内容,如果未能解决你的问题,请参考以下文章

Go select死锁分析

带有 select 语句的程序在 go 中逃脱死锁

MySQL死锁分析

select for update引发死锁分析

insert …select …带来的死锁问题

Mysql查询语句使用select.. for update导致的数据库死锁分析