Deadlock Beware: Using Channel and Range in Golang

Mipsmonsta
3 min readJan 24, 2022

In the course of learning about channel feature in Go or working with it, you may encounter situation where you would want to pull results from a channel via the range keyword versus the <- channel syntax.

For example, in the below function, you use a channel and send a sequence of numbers (up to n) to a integer channel and returned the channel.

package rangeChannelfunc TwoTimesSequence(n int, seed int) chan int { //return a channel
ch := make(chan int) //unbuffered channel
go func() {
a := seed
for i := 0; i < n; i++ {
ch <- a
a = a * 2
}
}()
return ch
}

In a another caller function, you could use range to obtain the result from the channel, such as:

package mainimport (
"fmt"
"rangeChannel"
)
func main() {
ch := rangeChannel.TwoTimesSequence(5, 2)
for val := range ch {
fmt.Printf("%v\n", val)
}
}

On the surface, the program should work. So, let’s see the output of the run.

$ go run main.go
2
4
8
16
32
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
C:/Users/user/Documents/Courses/Self Learning/Go/rangeChannel/example/main.go:11 +0x98
exit status 2

Expectedly, the doubling sequence of integer start with the seed value of 2 is obtained and printed out 5 times (as per what n is set to). Unexpectedly, we receive a fatal error exclaiming deadlock and that all go routines are asleep.

What happen?

In my article on channel and its blocking behavior, we learnt that if the sender or receiver is not balanced out with the other receiver or sender pulling or pushing data respectively, the said channel sender or receiver channel will block.

In this case example above, the single go routine did run to completion, since every time a new sequence value is sent via the channel, the range feature will pull out the value on the other end. Thus, the error that “all go routines are asleep” is not surprising. But still, what triggered the error?

Delving into range

Turns out, the pseudo code for the interaction between the range keyword and the channel could be depicted as below:

for {
val, ok := <-ch
if ok {
//do something with val }else {
break
}
}

In a glance, the range “loop” is never “stopped”. It’s always waiting for a next value from the receiver channel. However, the go routines had completed running at that point. Hence, the error.

What’s the solution?

Let’s do a deferred close of the channel when the go routine completes.

package rangeChannelfunc TwoTimesSequence(n int, seed int) chan int { //return a channel
ch := make(chan int) //unbuffered channel
go func() {
defer close(ch) //close the channel when go routine
//completes
a := seed
for i := 0; i < n; i++ {
ch <- a
a = a * 2
}
}()
return ch
}

The form

x, ok := <-c

will also set ok to false for a closed channel.

The standard documentation is very clear and encourages closing channel to ensure ok is false when we are done. The output when running the program is finally cleared of the error:

$ go run main.go
2
4
8
16
32

A little more tip, only close the channel from the sender ends.

I hope you learnt something today. In the mean time, have fun coding!

--

--

Mipsmonsta

Writing to soothe the soul, programming to achieve flow