for-loopgoconcurrencychanneldefer-keyword

Why doesn't deferred function execute?


I am trying hard to understand concurrency in Go.

package main

import "fmt"

func sendValues(myIntChannel chan int) {
    for i := 0; i < 5; i++ {
        myIntChannel <- i //sending value
    }
}

func main() {
    myIntChannel := make(chan int)
    defer close(myIntChannel)
    go sendValues(myIntChannel)

    for value := range myIntChannel {
        fmt.Println(value) //receiving value
    }
}

Above code is giving below output:

0
1
2
3
4
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
    /Users/spikki/Desktop/GoLearning/go_channel.go:51 +0x10b

As per my understanding, defer function will execute after completion of its surrounding functions. I am not able to interpret it.

If I use for loop to receive the values from the channel its working as below.

for i := 0; i < 5; i++ {
    fmt.Println(<-myIntChannel) //receiving value
}

Can anyone help me to understand this concept?


Solution

  • for ... range over a channel terminates only once all values have been received from the channel and the channel has been closed.

    In your example you wish to close the channel in a deferred function, but that would only run when main() returns. But main() would only return if the loop would end. This is the cause of deadlock. The for loop waits for the channel to be closed, and closing the channel waits for the for loop to end.

    When you use a loop to receive exactly 5 values form the channel, it works because the launched goroutines sends 5 values on it. This loop doesn't wait for the channel to be closed, so the loop can end, and so can the main() function.

    That's why the sender should close the channel (not the receiver), and problem is solved immediately:

    func sendValues(myIntChannel chan int) {
        for i := 0; i < 5; i++ {
            myIntChannel <- i //sending value
        }
        close(myIntChannel)
    }
    
    func main() {
        myIntChannel := make(chan int)
        go sendValues(myIntChannel)
    
        for value := range myIntChannel {
            fmt.Println(value) //receiving value
        }
    }
    

    Output (try it on the Go Playground):

    0
    1
    2
    3
    4