Search code examples
goconcurrencychannelgoroutine

Solving goroutines deadlock


I've been trying to solve this simple problem I encountered in Golang concurrency. I've been searching all possible solutions, but found nothing specific to my problem(or I might be missed one). Here's my code:

package main

import (
    "fmt"
    "time"
)

func producer(ch chan int, d time.Duration, num int) {

    for i:=0; i<num; i++ {
        ch <- i
        time.Sleep(d)
    }
}

func main() {
    ch := make(chan int)

    go producer(ch, 100*time.Millisecond, 2)
    go producer(ch, 200*time.Millisecond, 5)

    for {
        fmt.Println(<-ch)    
    }

    close(ch)
}

It prints error:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]: main.main() D:/Code/go/src/testconcurrency/main.go:23 +0xca exit status 2

What is the efficient way to avoid this error?, Thank you.


Solution

  • You need to synchronize all the asynchronous process in your goroutines. Your main thread and the goroutine threads are not synchronous process. Your main thread will never knew when to stop invoking channel from goroutines. Since your main thread loop over the channel, it always invoke the value from channel, and when the goroutines finished and the channel stop sending value, your main thread cannot get anymore value from the channel, hence the condition become deadlock. To avoid this use sync.WaitGroup to synchronize the asynchronous process.

    Here's the code:

    package main
    
    import (
        "fmt"
        "time"
        "sync"
    )
    
    func producer(ch chan int, d time.Duration, num int, wg *sync.WaitGroup) {
        for i:=0; i<num; i++ {
            ch <- i;
            time.Sleep(d);
        }
        defer wg.Done();
    }
    
    func main() {
        wg  := &sync.WaitGroup{}
        ch  := make(chan int);
    
        wg.Add(2);
        go producer(ch, 100*time.Millisecond, 2, wg);
        go producer(ch, 200*time.Millisecond, 5, wg);
    
        go func() {   
        wg.Wait()
        close(ch)
        }()
    
        // print the outputs
        for i:= range ch {
            fmt.Println(i);
        }
    }
    

    https://play.golang.org/p/euMTGTIs83g

    Hope it helps.

    Since my solution looks a little similar to already answered, I change it to my original answer before modification to suit OP question.

    Here's the code:

    package main
    
    import (
        "fmt"
        "time"
        "sync"
    )
    
    // producer produce values tobe sent to consumer
    func producer(ch chan int, d time.Duration, num int, wg *sync.WaitGroup) {
        defer wg.Done();
        for i:=0; i<num; i++ {
            ch <- i;
            time.Sleep(d);
        }
    }
    
    // consumer consume all values from producers
    func consumer(ch chan int, out chan int, wg *sync.WaitGroup) {
        defer wg.Done();
        for i:= range ch {
            out <- i
        }
    }
    
    // synchronizer synchronize all goroutines to avoid deadlocks
    func synchronizer(ch chan int, out chan int, wgp *sync.WaitGroup, wgc *sync.WaitGroup) {
        wgp.Wait()
        close(ch)
        wgc.Wait()
        close(out)
    }
    
    func main() {
        wgp  := &sync.WaitGroup{}
        wgc  := &sync.WaitGroup{}
        ch  := make(chan int);
        out := make(chan int);
    
        wgp.Add(2);
        go producer(ch, 100*time.Millisecond, 2, wgp);
        go producer(ch, 200*time.Millisecond, 5, wgp);
    
        wgc.Add(1);
        go consumer(ch, out, wgc)
    
        go synchronizer(ch, out, wgp, wgc)
    
        // print the outputs
        for i:= range out {
            fmt.Println(i);
        }
    }
    

    Using consumer goroutine to fan-in all input from multiple goroutines and read all values from the consumer goroutine.

    Hope it helps.