Search code examples
godeadlockchannelgoroutine

Why is this deadlocked?


I'm running this code in the Go Playground: https://play.golang.org/p/1gqgXZXDhsF but it ends in deadlock:

package main

import (
    "fmt"
    "time"
)

func createFakeReadData(r map[int]chan string, n int) {
    for {
        time.Sleep(time.Duration(n - 1) * time.Second)
        r[n] <- string(n)
    }
}

func log(l <-chan string) {
    fmt.Println(<-l)
}

func main() {
    logChan := make(chan string)
    go log(logChan)

    rChanMap := make(map[int]chan string)
    rChanMap[5] = make(chan string)
    rChanMap[6] = make(chan string)
    rChanMap[7] = make(chan string)
    rChanMap[8] = make(chan string)

    go createFakeReadData(rChanMap, 5)
    go createFakeReadData(rChanMap, 6)
    go createFakeReadData(rChanMap, 7)
    go createFakeReadData(rChanMap, 8)

    for {
        var f string
        select {
        case f = <-rChanMap[5]:
            logChan <- f
        case f = <-rChanMap[6]:
            logChan <- f
        case f = <-rChanMap[7]:
            logChan <- f
        case f = <-rChanMap[8]:
            logChan <- f
        }
    }

}
fatal error: all goroutines are asleep - deadlock!

I have 5 goroutines and a select statement in main that controls 4 of them (readers). The 5th (a logger) just sits and waits for anything to be sent to it on a channel. When one of the 4 readers gets input from a channel it should trigger a "case" in the select statement, then try to send that data onward to the logger channel.

I'm fairly new to go channels. I understand what deadlock is and some situations where it might occur, especially when using unbuffered channels. But this seems to me like it should work.

Thanks in advance for your help.


Solution

  • createFakeReadData is sending strings endlessly into channels like rChanMap[5]. But in log you only receive from the output channel once:

    func log(l <-chan string) {
        fmt.Println(<-l)
    }
    

    Since all the channels you created are unbuffered, if there's no goroutine receiving from the output channel logChan, the other goroutines would block forever trying to send to it.

    You may want to change the log function to be like:

    func log(l <-chan string) {
        for m := range l {
            fmt.Println(m)
        }
    }
    

    This way it would receive from the channel until it's closed.

    BTW, some free suggestions:

    1. string(n) doesn't convert an integer into string, try to search for the correct way to do this
    2. When you create a channel, you should think about when it should be closed, in your code no channels is closed
    3. Learn more about the difference between buffered and unbuffered channels