Goroutines with time.Sleep work, but WaitGroup causes errors – Synchronization Issue?

I am trying to synchronise 2 goroutines, like gr1 and gr2 run alternately.
For this I am using 2 channels to coordinate.

When I am using time.Sleep to wait for goroutines to complete, it is working without any error.

While when I use sync.Waitgroup to wait for goroutines it is throwing deadlock error.

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [semacquire]:
sync.runtime_Semacquire(0x14000002101?)
        /usr/local/go/src/runtime/sema.go:62 +0x2c
sync.(*WaitGroup).Wait(0x1027bd710)
        /usr/local/go/src/sync/waitgroup.go:116 +0x74

Program with time.Sleep

go playground link

package main

import (
    "fmt"
    "time"
)

func main() {

    ch1, ch2 := make(chan int, 1), make(chan int, 1)

    go f1(ch1, ch2)
    go f2(ch2, ch1)

    ch1 <- 1
    time.Sleep(time.Second * 1)
}

func f1(ch1, ch2 chan int) {
    for i := 1; i < 5; i++ {
        num := <-ch1
        fmt.Println("f1", num*i)
        ch2 <- num * i
    }
}

func f2(ch2, ch1 chan int) {
    for i := 1; i < 5; i++ {
        num := <-ch2
        fmt.Println("f2", num*i)
        ch1 <- num * i
    }
}

Program with Waitgroup

go playground link

package main

import (
    "fmt"
    "sync"
)

var wg sync.WaitGroup

func main() {

    ch1, ch2 := make(chan int), make(chan int)

    wg = sync.WaitGroup{}
    wg.Add(2)

    go f1(ch1, ch2)
    go f2(ch2, ch1)

    ch1 <- 1
    wg.Wait()
}

func f1(ch1, ch2 chan int) {
    for i := 1; i < 5; i++ {
        num := <-ch1
        fmt.Println("f1", num*i)
        ch2 <- num * i
    }
    wg.Done()
}

func f2(ch2, ch1 chan int) {
    for i := 1; i < 5; i++ {
        num := <-ch2
        fmt.Println("f2", num*i)
        ch1 <- num * i
    }
    wg.Done()
}

For buffered channel there will be no error.

I like to understand what waitgroup is doing different than the time.sleep.

Thanks

  • 2

    I’m a bit confused. In the question, you write “For buffered channel there will be no error”; so why are you asking about WaitGroup? I haven’t tested it, but it seems pretty clear that the version with Sleep will also deadlock with an unbuffered channel and the issue is not WaitGroup at all.

    – 

  • @WilliamPursell the version with Sleep doesn’t deadlock because main isn’t blocked. main– and thus the process – ends after two seconds, silently terminating remaining f2 goroutine as it waits to ch1<-num*i one last time.

    – 




In the waitgroup example, you’ve provided, in f2 func and in the last iteration ch1 <- num * i tries to write into ch1, while there is no reader available for that channel, and since it’s an unbuffered channel:

A receive from an unbuffered channel is synchronised before the completion of the corresponding send on that channel. (https://go.dev/ref/mem#chan)

Since f2 is stalled on that line it never reaches wg.Done() and wg.Wait throws a deadlock panic.

Changing f2 as so can fix the issue while there are better options:

func f2(ch2, ch1 chan int) {
    for i := 1; i < 5; i++ {
        num := <-ch2
        fmt.Println("f2", num*i)
        if i < 4 {
            ch1 <- num * i
        }
    }
    wg.Done()
}

Leave a Comment