Search code examples
godeadlockgoroutine

All goroutines are sleep deadlock


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.