Go select is deadlocked...

Keywords: Go

 

Original text: https://mp.weixin.qq.com/s?__biz=MzAxNzY0NDE3NA==&mid=2247488032&idx=1&sn=5d0c370dd992e1dd82030e5ac5600244&chksm=9be33dc1ac94b4d7798bb7ed230c899088a5c972c1c099e48779505f89539f0a840520cd0d4d&scene=178&cur_album_id=1877309550266007553#rd

--------------------

 

Hello, I'm polarisxu.

Two days ago, Huoding wrote an article: "a select deadlock problem" [1], which is another small detail. I will change the question to better understand:

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

As a general understanding, select in go func should execute the default branch, and the program runs normally. But the result is not, but deadlock. You can test through this link: https://play.studygolang.com/p/kF4pOjYXbXf .

The reason is also explained in the article. There is a sentence in the Go language specification:

For all the cases in the statement, the channel operands of receive operations and the channel and right-hand-side expressions of send statements are evaluated exactly once, in source order, upon entering the "select" statement. The result is a set of channels to receive from or send to, and the corresponding values to send. Any side effects in that evaluation will occur irrespective of which (if any) communication operation is selected to proceed. Expressions on the left-hand side of a RecvStmt with a short variable declaration or assignment are not yet evaluated.

I wonder if you understand? So, finally, an example is given to verify whether you understand: why do you output half of the data every time and then deadlock? (similarly, you can run here to view the results: https://play.studygolang.com/p/zoJtTzI7K5T )

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

Do you have this feeling:

Introduction to algorithm

This is a problem on StackOverflow: https://stackoverflow.com/questions/51167940/chained-channel-operations-in-a-single-select-case .

The key point, like the example at the beginning of the article, is that the two channel s in the select case are connected in series, that is, in the fanIn function:

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

If you change to this, everything is normal:

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

Combined with this more complex example, analyze the sentence in the Go language specification.

For the select statement, when entering the statement, each case clause will be evaluated in the order of the source code: this evaluation is only for the additional expression of the send or receive operation.

For example:

// ch is a chan int;
//  getVal()   return   int
//  input   yes   chan   int
//  getch()   return   chan   int
select {
  case ch <- getVal():
  case ch <- <-input:
  case getch() <- 1:
  case <- getch():
}

Before a specific case is selected for execution, the   getVal(),<-input   and   getch()   Will execute. Here is an example of verification: https://play.studygolang.com/p/DkpCq3aQ1TE .

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 be executed in the order of the source code: getVal(1)   and   getVal(2), that is, they must output first:

getVal, i= 1
getVal, i= 2

You can think it over.

Now go back to the problem on StackOverflow.

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, only one case will be select ed for execution each time, so  <- input1   and  <- input2   As a result, one must be discarded, that is, it will not be written to 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)

The main loops 10 times and only gets the result 5 times, so the deadlock is reported after 5 times of output.

Although this is a small detail, it is still possible in actual development. For example, the example mentioned in the article is written as follows:

// ch is a chan int;
//  getVal()   return   int
//  input   yes   chan   int
//  getch()   return   chan   int
select {
  case ch <- getVal():
  case ch <- <-input:
  case getch() <- 1:
  case <- getch():
}

Therefore, when using select, you must pay attention to this possible problem.

Don't think this problem will not be encountered. In fact, it is very common. The most common problem is the memory leak caused by time.After. There are many articles on the Internet explaining the reasons and how to avoid it. In fact, the most fundamental reason is the mechanism of select.

For example, the following code has a memory leak (the larger the time parameter passed to time.After, the worse the leak will be). Can you explain the reason?

package main

import (
    "time"
)

func main()  {
    ch := make(chan int, 10)

    go func() {
        var i = 1
        for {
            i++
            ch <- i
        }
    }()

    for {
        select {
        case x := <- ch:
            println(x)
        case <- time.After(30 * time.Second):
            println(time.Now().Unix())
        }
    }
}

reference material

[1]

A select deadlock problem:   https://blog.huoding.com/2021/08/29/947

Posted by stratguitar on Sun, 05 Dec 2021 06:53:47 -0800