go routines deadlocked even if channel was closed

Issue

I have a list, with a function that pop element from it, and another function that “receives” the popped elements. I thought that putting a close after the receiver would close the channel, but it seems that the program is deadlock before getting there. Which is the best way of doing this? Should I have another channel that detects when the pop are done?

Playground link

func pop(list *[]int, c chan int) {
    if len(*list) != 0 {
        result := (*list)[0]
        *list = (*list)[1:]
        fmt.Println("about to send ", result)
        c <- result
    } else {
        return
    }
}

func receiver(c chan int) {

    result := <-c
    fmt.Println("received ", result)
}

var list = []int{1, 2, 3}

func main() {

    fmt.Println("Main")
    c := make(chan int)
    go pop(&list, c)
    go pop(&list, c)
    for len(list) > 0 {
        receiver(c)
    }
    close(c) //Dosen't seem to have any effect
    fmt.Println("done")

}

Solution

There are so many problems with the code, let’s see.

  1. your pop function doesn’t lock when accessing the slice, so that’s a data race right there.
  2. for len(list) > 0 {} is a data race because you’re accessing list while modifying it in 2 other goroutines.
  3. for len(list) > 0 {} will never return because you have 3 items in your list but you call pop only twice.
  4. receiver(c) errors because of #3, it tries to read from the channel but there’s nothing writing to it.

One way to do it is to use one writer (pop) and multiple readers (receiver):

func pop(list *[]int, c chan int, done chan bool) {
    for len(*list) != 0 {
        result := (*list)[0]
        *list = (*list)[1:]
        fmt.Println("about to send ", result)
        c <- result
    }
    close(c)
    done <- true
}

func receiver(c chan int) {
    for result := range c {
        fmt.Println("received ", result)
    }
}

var list = []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

func main() {
    c := make(chan int)
    done := make(chan bool)
    go pop(&list, c, done)
    go receiver(c)
    go receiver(c)
    go receiver(c)
    <-done
    fmt.Println("done")
}

playground

Always use go run -race blah.go when messing with goroutines.

Answered By – OneOfOne

Answer Checked By – Cary Denson (GoLangFix Admin)

Leave a Reply

Your email address will not be published.