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
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
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
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()
}
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 becausemain
isn’t blocked.main
– and thus the process – ends after two seconds, silently terminating remainingf2
goroutine as it waits toch1<-num*i
one last time.