Pzhang
Pzhang

Reputation: 361

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?

Upvotes: 4

Views: 3966

Answers (3)

E-WAVE
E-WAVE

Reputation: 117

In addition to Jack Yu's answer, It should be noted that when using the select statement to handle communication amongst multiple channels, you should use the return keyword as opposed to the break keyword i.e.

if !ok {
fmt.Println("channel closed!")
return
}

This is because the break statement in designed to handle the nearest outer loop function (which in this case is the select keyword) as opposed to the main for loop

Upvotes: 0

Jack Yu
Jack Yu

Reputation: 2425

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.

Upvotes: 3

Alejandro Alvarado
Alejandro Alvarado

Reputation: 396

I guess you need to verify that the channel can still receive messages; if not, simply use "break" to exit from the for-loop

package main

import (
    "fmt"
    "time"
)

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:
            if !ok {
                break
            }
            time.Sleep(time.Second * 2)
            fmt.Println("The case intChan ok: ", ok)
            fmt.Println("The case intChan is selected.", e)
            close(intChan)

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

Upvotes: 0

Related Questions