I have a function which takes an int array and dumps them in a channel.
func Dump(a []int, ch chan int) {
for i := range a {
ch <- i
}
close(ch)
}
This main doesn't build:
func main() {
ch := make(chan int)
arr := []int{1, 2, 3, 4, 5}
Dump(arr, ch)
for i := range ch {
fmt.Printf("Got %v
", i)
}
}
throws this error:
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.Dump(0xc000078f48, 0x5, 0x5, 0xc00006e060)
/Users/300041738/go-workspace/src/test.go:7 +0x43
main.main()
/Users/300041738/go-workspace/src/test.go:15 +0x9b
exit status 2
However, this builds:
func main() {
ch := make(chan int)
arr := []int{1, 2, 3, 4, 5}
go Dump(arr, ch)
for i := range ch {
fmt.Printf("Got %v
", i)
}
}
Why do I have to write go infront of Dump? I don't wish to dump the contents of the array asynchronously.
The root of your problem - when you writing to unbuffered channel it's locked until someone read the value from the channel. For unbuffered channel - after each write operation you have to do one read operation. Else your second write operation will be locked until someone read the first value. In your first example, it will never happen.
To illustrate:
Dump()
function invokedfor
loopi
value to the channelfor
loopi
value to the channel, but it locks, nobody has read the first value.This happens when adding the goroutine usage (go Dump(arr, ch)
):
Dump()
function invoked in the separate goroutinefor
loopi
value to the channelfor
loopi
value to the channel, but it locks, nobody has read the first value.Dump()
), but we have the main goroutine and go scheduler switches the control to the main goroutine.for i := range ch
. And it finally reads the first value!Dump()
goroutine is unlocked and it's possible to write the second value.Note, the exact order of execution can differ (based on the logic of go scheduler). To play with it, you can add print into Dump()
function for the case with go Dump(arr, ch)
:
func Dump(a []int, ch chan int) {
for i := range a {
fmt.Printf("Write %v
", i)
ch <- i
}
close(ch)
}
You will see that Write and Got messages will intermix.
It's hard to provide a solution for your answer because it's a sandbox example and the channel is not needed there in reality.
By using buffered channel, let's say with size n
, you can do n
writes without locking and reads. I would recommend you to review the basics of Go channels.
Channels have buffers. By default the buffer is of size 0. In other words, if an element is to be inserted to a non-buffered channel, the inserting goroutine will halt till another goroutine retrieves the value from the channel.
So for fun, try this :
import "fmt"
func Dump(a []int, ch chan int) {
for i := range a {
ch <- i
}
close(ch)
}
func main() {
arr := []int{1, 2, 3, 4, 5}
ch := make(chan int, len(arr)) //specify channel buffer length equal to arr size
Dump(arr, ch)
for {
i, ok := <- ch
if ok {
fmt.Println("received a number !", i)
} else {
fmt.Println("channel is closed, we're done here")
}
}
}
A dealock is caused because your main goroutine is trying to write to a channel but nobody is reading from it. You don't need to use buffered channels here.
There is a concept of pipelining in go. Your Dump
function is basically acting like the source of the pipeline here. You can modify your Dump
function to something like this:
func Dump(a []int) chan int {
ch := make(chan int)
go func() {
for i := range a {
ch <- i
}
close(ch)
}()
return ch
}
Notice I'm writing into the channel in a separate go routine now.