the following goroutine code returns deadlock...
Please help me to identify the issue package main
import "fmt"
func main() {
// create & consume channel
counter := make(chan int)
even := make(chan int)
odd := make(chan int)
square := make(chan int)
merge := make(chan int)
out := make(chan struct{})
//GoRoutines
go counterFn(counter)
go squarerFn(counter, square)
go counterSplit(counter, even, odd)
go merger(square, odd, merge)
go printOut(merge, out)
<-out
}
func counterFn(counter chan int) {
for i := 0; i < 5; i++ {
counter <- i
}
close(counter)
}
func squarerFn(counter chan int, square chan int) {
for i := range counter {
square <- i * i
}
close(square)
}
func counterSplit(counter chan int, even chan int, odd chan int) {
for i := range counter {
if i%2 == 0 {
even <- i
} else {
odd <- i
}
}
close(even)
close(odd)
}
func merger(even chan int, odd chan int, merge chan int) {
i := 0
for {
fmt.Printf("%d \n", i)
a, ok := <-even
if !ok {
i++
} else {
merge <- a
}
a, ok = <-odd
if !ok {
i++
} else {
merge <- a
}
if i == 2 {
break
}
}
close(merge)
}
func printOut(merge chan int, out chan struct{}) {
for i := range merge {
fmt.Print(i)
}
close(out)
}
Here is the output
go run channel_pipelineAdv.go
0 , 0
1
0 , 9
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
D:/channel_pipelineAdv.go:20 +0x233
goroutine 7 [chan send]:
main.squarerFn(0x0?, 0x0?)
D:/channel_pipelineAdv.go:32 +0x45
created by main.main in goroutine 1
D:/channel_pipelineAdv.go:16 +0x114
goroutine 8 [chan send]:
main.counterSplit(0x0?, 0x0?, 0x0?)
D:/channel_pipelineAdv.go:40 +0x5e
created by main.main in goroutine 1
D:/channel_pipelineAdv.go:17 +0x179
goroutine 9 [chan receive]:
main.merger(0x0?, 0x0?, 0x0?)
D:/channel_pipelineAdv.go:59 +0xca
created by main.main in goroutine 1
D:/channel_pipelineAdv.go:18 +0x1d9
goroutine 10 [chan receive]:
main.printOut(0x0?, 0x0?)
D:/channel_pipelineAdv.go:73 +0x77
created by main.main in goroutine 1
D:/channel_pipelineAdv.go:19 +0x227
exit status 2
PS D:\Study\Go\GoLearn\basic>
Thanks in advance
I'd say the code of merger
is problematic: its relying on incrementing of i
up to a limit of 2 appears to be based on the expectation that both even
and odd
are closed roughly at the same time, and then a read from even
returning false
as its second value will be followed by a read from odd
–also returning false
as its second value.
This expectation is unfounded: any goroutine might get "stalled" for whatever reason which would make other goroutines do "more" progress.
Think what happens if the goroutine sending to odd
gets way less computing quanta than that sending to even
.
In this scenario, suppose that even
is closed, and odd
is not and there will be two or more sends (and matching receives) performed on it. The loop in merger
will execute a "no-op" receive from the closed even
and increment i
by one, then a "true" receive from odd
(and not touch i
), then another successful "no-op" receive from even
and bump i
to 2, and then another "true" receive from odd
and then exit as i
is 2 even though there may be more sends to be done over odd
.
We could fix your code by employing an idiom of using the switch
statement operating on more than a single channel in a loop combined with "disabling" the channels which are "known to be done with":
package main
import "fmt"
func main() {
// create & consume channel
counter := make(chan int)
even := make(chan int)
odd := make(chan int)
square := make(chan int)
merge := make(chan int)
out := make(chan struct{})
//GoRoutines
go counterFn(counter)
go squarerFn(counter, square)
go counterSplit(counter, even, odd)
go merger(square, odd, merge)
go printOut(merge, out)
<-out
}
func counterFn(counter chan int) {
for i := 0; i < 5; i++ {
counter <- i
}
close(counter)
}
func squarerFn(counter chan int, square chan int) {
for i := range counter {
square <- i * i
}
close(square)
}
func counterSplit(counter chan int, even chan int, odd chan int) {
for i := range counter {
if i%2 == 0 {
even <- i
} else {
odd <- i
}
}
close(even)
close(odd)
}
func merger(even chan int, odd chan int, merge chan int) {
for {
select {
case a, ok := <-even:
if ok {
merge <- a
} else {
even = nil
}
case b, ok := <-odd:
if ok {
merge <- b
} else {
odd = nil
}
default:
close(merge)
return
}
}
}
func printOut(merge chan int, out chan struct{}) {
for i := range merge {
fmt.Print(i)
}
close(out)
}
(Playground.)
Note that there, once we're done with even
, we overwrite it with nil
which will make the next attempt to read from it block indefinitely, in effect making select
always prefer another branch–if any. Eventually disabling odd
in the same way (by now, you should understand that odd
might as well get closed earlier than even
, I'm just continuing the first part of the explanation) will make select
prefer the only remaining branch, default
, which will make it close the resulting channel and exit.