了解去通道僵局

package main

import (
    "fmt"
    "time"
)

func main() {
    p := producer()
    for c := range p {
        fmt.Println(c)
    }
}

func producer() <-chan string {
    ch := make(chan string)
    go func() {
        for i := 0; i < 5; i++ {
            ch <- fmt.Sprint("hello", i)
            time.Sleep(1 * time.Second)
        }
        // commented the below to show the issue
        // close(ch)
    }()
    return ch
}

Running the above code will print 5 messages and then give a "all go routines are a sleep - deadlock error". I understand that if I close the channel the error is gone.

The thing I would like to understand is how does go runtime know that the code will be waiting infinitely on the channel and that there is nothing else that will be sending data into the channel.

Now if I add an additional go routine to the main() function.. it does not throw any error and keeps waiting on the channel.

go func() {
        for {
            time.Sleep(2 * time.Millisecond)
        }
    }()

So does this mean.. the go runtime is just looking for presence of a running go routine that could potentially send data into the channel and hence not throwing the deadlock error ?

The runtime panics with the "all go routines are a sleep - deadlock error" error when all goroutines are blocked on channel and mutex operations.

The sleeping goroutine does not block on one of these operations. There is no deadlock and therefore no panic.

If you want some more insight into how Go implements the deadlock detection, have a look at the place in the code that throws the "all goroutines are asleep - deadlock!": https://github.com/golang/go/blob/master/src/runtime/proc.go#L3751

It looks like the Go runtime keeps some fairly simple accounting on how many goroutines there are, how many are idle, and how many are sleeping for locks (not sure which one sleep on channel I/O will increment). At any given time (serialized with the rest of the runtime), it just does some arithmetic and checks if all - idle - locked > 0... if so, then the program could still make progress... if it's 0, then you're definitely deadlocked.

It's possible you could introduce a livelock by preventing a goroutine from sleeping via an infinite loop (like what you did in your experiment, and apparently sleep for timers isn't treated the same by the runtime). The runtime wouldn't be able to detect a deadlock in that case, and run forever.

Furthermore, I'm not sure when exactly the runtime checks for deadlocks- further inspection of who calls that checkdead() may yield some insight there, if you're interested.

DISCLAIMER- I'm not a Go core developer, I just play one on TV :-)