All goroutines are sleep deadlock

Issue

Simulating my real problem I have this code.
Basically, each element of the array "letters" along with its index is sent to a goroutine to compare it with "x", then it sends a response through the channel.
My idea is that it runs on "x" threads, in the real case I use 8 threads.

package main

import (
    "strconv"
    "sync"
)

var wg sync.WaitGroup
const sizeLetters = 12

func detectX(ch2 chan int, j int, letters [sizeLetters]string) {
    if letters[j] == "x" {
        ch2 <- j
    }else{
        ch2 <- -1
    }
}


func main() {
    ch1 := make(chan int)
    ch2 := make(chan int)
    letters := [sizeLetters]string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"}
    threads:= 4
    wg.Add(threads)
    for i := 0; i < threads; i++ {
        go func() {
            for {
                j, ok := <-ch1
                if !ok {
                    wg.Done()
                }
                detectX(ch2, j, letters)
            }
        }()
    }
    for i := 0; i < sizeLetters; i++ {
        ch1<-i // add i to the queue
    }
    k, ok := <-ch2 //k contains the position of X, if exist
    if !ok {
        wg.Done()
    }
    if k != -1 { //when exist
        println("X exist in position: " + strconv.Itoa(k))
    }
    println("X doesn´t exist")
    close(ch2)
    close(ch1)
    wg.Wait()
}

Solution

Insufficient reputation to comment. So, in lieu of a comment, here is an alternate version of the code:

package main

import (
    "fmt"
    "sync"
)

var wg sync.WaitGroup

const sizeLetters = 12

func detectX(ch2 chan int, j int, letters [sizeLetters]string) {
    if letters[j] == "x" {
        ch2 <- j
    }
}

func main() {
    ch1 := make(chan int)
    ch2 := make(chan int)
    letters := [sizeLetters]string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"}
    threads := 4
    wg.Add(threads)
    for i := 0; i < threads; i++ {
        go func() {
            for {
                j, ok := <-ch1
                if !ok {
                    wg.Done()
                    return
                }
                detectX(ch2, j, letters)
            }
        }()
    }
    // Use a goroutine to close ch2. It is only safe to do this
    // after all the other goroutines have exited.
    go func() {
        wg.Wait()
        close(ch2)
    }()
    for i := 0; i < sizeLetters; i++ {
        ch1 <- i // add i to the queue
    }
    close(ch1)
    if k, ok := <-ch2; ok && k != -1 { //when exist
        fmt.Println("X exist in position:", k)
    } else {
        fmt.Println("X doesn´t exist")
    }
}

It still has some data dependent issues (unless the letters array is guaranteed to not contain duplicates):

  • Namely, if there is more than one "x" in the array, the goroutines will not all exit. That is, main() does not drain ch2.
  • If there are as many as threads "x" values, then the code will deadlock in the top level for loop in main() since the writes to ch1 will run out of unblocked goroutines to consume them.
    • If you know how many "x" values are possible in the letters array, you could make the ch2 channel that deep: ch2 := make(chan int, depth). This will allow all the goroutines to exit, but ch2 will potentially still contain undrained data.

Answered By – Tinkerer

Answer Checked By – Jay B. (GoLangFix Admin)

Leave a Reply

Your email address will not be published.