I'm trying to learn Go and I'm using this tutorial.
I've written the following code,
var wg sync.WaitGroup
func foo(c chan int, someValue int) {
defer wg.Done()
c <- someValue * 5
}
func main() {
fooVal := make(chan int)
for i := 0; i < 10; i++ {
go foo(fooVal, i)
wg.Add(1)
}
wg.Wait() // Wait for all routines to complete
close(fooVal) // close channel
for item := range fooVal {
fmt.Println(item)
}
}
Here is my understanding so far,
However, I get an error that says:
fatal error: all goroutines are asleep - deadlock!
I'm not sure what this means. My guess is that range
tries to get a value from the channel but it doesn't have any. But that shouldn't be happening because I waited for all routines to complete and then I closed the channel.
What's going on?
The solution for this is to do something like make(chan int, 10)
to give it a buffer, but I'm not sure what a buffer is or why I need it.
Also, I'm not sure what make
does. I've used it to create a map as well. Is it just a constructor?
The reason all of your goroutines are asleep is because by default a channel blocks it's goroutine on send until the value is received. See https://tour.golang.org/concurrency/2
By default, sends and receives block until the other side is ready. This allows goroutines to synchronize without explicit locks or condition variables.
A buffer resolves this by allowing a channel to 'hold' that many values without blocking. Think of a buffered channel as a bucket that can hold N items (where N is the buffer size, 10 in your case) in order to put more into the bucket, you need to wait for something to be removed (i.e. read). By default a channel is unbuffered, and it's value must be received before it will unblock it's sending goroutine.
In your code you have goroutines attempting to put items into your unbuffered channel, so the first routine waits for the item it put into the channel to be read. But your channel will never empty until the range statement reads it, which will never fire until the channel empties and the goroutine finishes (because of wg.Wait()), which is a deadlock. Neither can proceed.
With a buffer, the buffered channel tells your goroutines it's ok to finish without it's value being read (up to 10 items in your case) and so the waitgroup finishes, and the range statement reads all the values out as expected.
As for make, thinking of it as a constructor is good enough, but know that it's only used for slices, channels and maps. Those are special types in go and can't be created like regular structs. (I believe it has to do with the fact that each of those acts generically, i.e. it can hold items of any specified type, and that's why make is needed, but I'm not 100% on that).
This is a fairly simple misunderstanding of how channels work.
"By default, sends and receives block until the other side is ready." - https://tour.golang.org/concurrency/2
You have created a single channel to receive all results from your goroutines, and you've created 10 routines. After your create loop, you immediately wait for all routines to exit... but the parent is making no attempt to read data out of the channel.
Since the default size of the channel is 1, the channel blocks until the receiver reads the first int out of it. So only the first sub-routine will ever write and exit. The other 9 will be waiting for the parent to clear out the channel. Since the sub-routines are blocking on the channel, and the parent is blocking on the wait, you have a deadlock.
The buffer simply allows the channel to hold N more results before blocking kicks in. If you make the buffer big enough to hold inputs from every routine, then the routines are able to exit, and you get out of your wait condition.