Thoughts on Go select deadlock
https://mp.weixin.qq.com/s/Ov1FvLsLfSaY8GNzfjfMbg Continuous thinking triggered by the article
Summary above
Summary I
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 }
No matter which case is finally selected by select, getVal() will execute in the order of source code: getVal(1) and getVal(2), that is, they must output first:
getVal, i= 1 getVal, i= 2
Summary II
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) } }
Each time you enter the following select statement:
select { case ch <- <-input1: case ch <- <-input2: }
< - input1 and < - input2 will be executed, and the corresponding values are: A x and B x (where x is 0-5). However, each select will only select one of the case s for execution, so one of the results of < - input1 and < - input2 must be discarded, that is, it will not be written into ch. Therefore, a total of only 5 times will be output, and the other 5 times will be lost. (you will find that among the five output results, X is 0 1 2 3 4, for example)
In main, there are 10 cycles and only 5 results are obtained, so a deadlock is reported after 5 times of output.
If you change to this, everything is normal:
select { case t := <-input1: ch <- t case t := <-input2: ch <- t }
My understanding:
Case ch < - < - input: the statement is executed in two sections, which can be understood as
t := <- input //When the case selection is not clear, it will be executed ch <- t //If this case is not selected, this statement is not executed And these are two statements, in order therefore<-input This is not selected after execution caseļ¼<-input The result will be discarded, resulting in the above deadlock problem.
Extension of the problem
Mentioned above
No matter which case is finally selected by select, getVal() will execute in the order of source code: getVal(1) and getVal(2), that is, they must output first:
getVal, i= 1 getVal, i= 2
Think 1: if the execution time of getVal() method is different, does the running time of select depend on the running time or the sum of the running time?
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) }
Output results
getVal, i= 1 getVal, i= 2 3.0015862s getVal, i= 1 getVal, i= 2 3.0021938s getVal, i= 1 getVal, i= 2 3.0019246s
It can be seen that each select will execute the case statements in order, and the execution time of the select is the sum of the case statements
Of course, there will be no such writing in actual production
Correct writing:
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)) }
The output result depends on the longest running getVal()
getVal, i= 1 1 getVal, i= 2 2 2.0020979s
In actual production, the select statement is only used to accept the value in the channel, not to execute a method
Careful friends have found that there are two bug s in the above writing
- In a new start-up process, the for statement causes it to idle all the time, and the process will not be destroyed
- If you send data to ch after it is close d, panic will result
Add some comments to see the output
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) }
Output results
getVal, i= 1 getVal, i= 2 goroutine num 2 1 2 2.0020965s goroutine num 2 panic err send on closed channel
It can be seen that the coroutine of the for loop is not released, and a panic exception is also reported in the subsequent ch < - operation