Search code examples
gogoroutine

when golang close the channel, the receiver goroutine is never blocked


I wrote some code to learn Go channels, a piece of code like this below:

func main(){
    intChan := make(chan int, 1)
    strChan := make(chan string, 1)

    intChan <- 3
    strChan <- "hello"
    fmt.Println("send")

    // comment this, fatal error: all goroutines are asleep - deadlock!
    // close(intChan)
    // close(strChan)

    for {
        select {
        case e, ok := <-intChan:
            time.Sleep(time.Second * 2)
            fmt.Println("The case intChan ok: ", ok)
            fmt.Println("The case intChan is selected.", e)

        case e, ok := <-strChan:
            time.Sleep(time.Second)
            fmt.Println("The case strChan ok: ", ok)
            fmt.Println("The case strChan is selected.", e)
        }
    }
}

If I comment the close() function, the "for" statement is blocked, just as the error says "all goroutines are asleep - deadlock!", it seems reasonable.

If I uncomment the close(), the "for" statement never stops. The receiver gets default 0 and nil from the channel and never blocks.

Even if I send nothing to the channel, and call the close() after the channel is defined. The receiver never blocks or causes any error.

I am confused about what the close function does, does it start a go routine to send the default value of a specific type to the channel, and never stop?


Solution

  • When channel is closed, you still could read it, but your ok will be false. So, that's why your for never stops. And, you need to break for statement by condition if !ok { break }.

    When channel is not closed, it will be blocked or not be blocked depend on buffered / unbuffered channel when you try to read data from it.


    The buffered channel: you give the size (make(chan int, 1)).
    The unbuffered channel: you don't give the size (make(chan int))


    In your case which comment close, it will read data from you buffered channel once because you send data to channel by intChan <- 3 and strChan <- "hello". So, you will see your console print the following result.

    send
    The case strChan ok:  true
    The case strChan is selected. hello
    The case intChan ok:  true
    The case intChan is selected. 3
    

    But, after that, the both buffered channels don't have data any more. Then, if you try to read it, you will be blocked because there is no any data in buffered channel. (Actually, unbuffered is same situation when there is no data in it.)

    Then, you get the all goroutines are asleep - deadlock because the main goroutine is blocked to wait for data from channel. By the way, when you run this program, it will start a main goroutine to run your code. If this only one main goroutine is blocked, that means there is no anyone could help you to run your code.

    You could add the following code before for statement.

    go func() {
        fmt.Println("another goroutine")
        for {}
    }()
    

    You will see you don't get the deadlock because there is still a goroutine ""might"" run your code.