Why there is no error that receiver is blocked?

Issue

According to Go documentation:

Receivers always block until there is data to receive

This test should fail, because for last receiving operation from a channel there is no corresponding write :

package main

import "fmt"

func main() {
    c := make(chan int)    
    for i := 0; i < 4; i++ { // 4 async reads
      go func() {
            fmt.Println("received:", <-c)
         }()

    }    
    // just 3 writes, 1 write is missing
    c <- 1   
    c <- 2 
    c <- 3   
}

However script not fails with error message in read goroutine, but it succeeds printing 3 values :

received: 1
received: 2
received: 3

Why it is so, or I misunderstood something about synchronization ?

Solution

There is no deadlock here because the main goroutine is not blocked. It sends 3 values on c which succeed because there are 4 launched goroutines receiving from it, and then it ends. And with it ends your app as well, it does not wait for other non-main goroutines to end. See No output from goroutine.

A deadlock implies all goroutines to be blocked. That’s not the case here.

It is not an error to try to receive from a channel where there is no one (currently or ever) ready to send on. If fact, that’s completely normal. That’s one of the use-cases of channels: it acts as a synchronization tool, you can send / receive and the operation will block until the other end is ready as well.

There are cases when even a goroutine blocked for the entire app lifetime is normal, e.g. a goroutine may wait for user input such as CTRL+BREAK, which the user may never press and the app may end normally.

So this is not considered an error, and no error or warning messages are printed for these. But if you’re curious, it’s easy to implement it. Just add a deferred function to your main() which will be called last thing before your main() function (and your app with it) ends. In that print the number of running goroutines:

func main() {
    defer func() {
        fmt.Println("Remaining goroutines:", runtime.NumGoroutine()-1) //-1 for main
    }()

    // your code
}

With this addition, output will be:

received: 1
received: 2
received: 3
Remaining goroutines: 1

If you change your loop to launch 14 goroutines instead of 1, output will say there are 11 remaining goroutines.

Last note: since in your app the main() function does not wait for other goroutines to end, they may still be active at the time when deferred functions are called, so they may or may not be included in the remaining goroutines count. If you would use e.g. sync.WaitGroup to wait for them to end, then they certainly wouldn’t be included. See Prevent the main() function from terminating before goroutines finish in Golang for an example.

Answered By – icza

Answer Checked By – Gilberto Lyons (GoLangFix Admin)

Leave a Reply

Your email address will not be published.