Search code examples
gochannelgoroutine

Why channel does not get closed?


For the below code:

package main

import "fmt"

func emit(c chan string) {
    words := []string{"The", "quick", "brown", "fox", "ran", "away"}

    for _, word := range words {
        fmt.Printf("send %s\n", word)
        c <- word
    }
    fmt.Printf("Close the channel\n")
    close(c)
}

func main() {
    wordChannel := make(chan string)

    go emit(wordChannel)

    word := <-wordChannel
    fmt.Printf("receive %s \n", word)

    word = <-wordChannel
    fmt.Printf("receive %s \n", word)

    word = <-wordChannel
    fmt.Printf("receive %s \n", word)

    word, ok := <-wordChannel
    fmt.Printf("receive %s %t\n", word, ok)

    word, ok = <-wordChannel
    fmt.Printf("receive %s %t\n", word, ok)

    word, ok = <-wordChannel
    fmt.Printf("receive %s %t\n", word, ok)

}

Below is the output:

send The
send quick
receive The 
receive quick 
send brown
send fox
receive brown 
receive fox true
send ran
send away
receive ran true
receive away true

Why sender go-routine does not close the channel?


Solution

  • Your main goroutine ends without coordinating with the emit goroutine to know that it's finished. When the end of main is reached, the program - and all its goroutines - ends regardless of whether any go routines may still be processing, unless you explicitly wait for them.

    Closing the channel can help communicate the emit goroutine's completion to main. In this case, and using the two-value response from the channel read as in

    word, ok = <-wordChannel
    

    does expose the state of the channel (open or closed) to main, but you never use it to control the flow of main. Further, you have an exact the number of reads hard coded into main. So, even if you were controlling flow with a channel close , you would never attempt a final read to see the closed channel.

    Luckily, the solution with go can be quite simple. rangeing over a go channel will read values until the channel is closed. So you can simplify your code, remove the explicit number of channel receives, and use the channel close to signify emit's completion, all with this more concise version:

    package main
    
    import "fmt"
    
    func emit(c chan string) {
        words := []string{"The", "quick", "brown", "fox", "ran", "away"}
    
        for _, word := range words {
            fmt.Printf("send %s\n", word)
            c <- word
        }
        fmt.Printf("Close the channel\n")
        close(c)
    }
    
    func main() {
        wordChannel := make(chan string)
        go emit(wordChannel)
        for word := range wordChannel {
          fmt.Printf("receive %s \n", word)
        }
    }
    

    When I run this, I get what I perceive to be your desired output:

    $ go run t.go
    send The
    send quick
    receive The
    receive quick
    send brown
    send fox
    receive brown
    receive fox
    send ran
    send away
    receive ran
    receive away
    Close the channel
    

    This "range over a channel" syntax is conceptually equivalent to something like this, just a little more elegant looking:

    func main() {
        wordChannel := make(chan string)
        go emit(wordChannel)
        for {
          if word, ok := <-wordChannel; ok {
            fmt.Printf("receive %s \n", word)
          } else {
            break
          }
      }
    }