I'm new to Golang and I have hard time figuring out why exactly the following code produces deadlock. Also, how can I fix it so it works?
package main
import "fmt"
func main() {
m := make(map[int]chan string)
go func() {
m[0] = make(chan string)
m[0] <- "abab"
}()
fmt.Println(<-m[0])
}
EDIT:
Thanks for your answers! Unfortunately, initializing m[0]
with
m[0] = make(chan string)
before launching a new goroutine is not exactly what I want. My problem is: is there a way to create channels "dynamically"? E.g. I have a map m
of type map[int]chan string
and I receive requests that contain something like id
of type int
. I would like to send a message via channel map[id]
, but initializing channels for every int
would be too costly. How do I solve/work around this?
So, in other words, I would like to have a separate job queue for every id
and initialize each queue lazily.
CodePudding user response:
Updated answer after OP updated the question
You can just loop on all the keys in your map, maybe have another goroutine that keeps looping on all the keys. Obviously if a key hasnt been initialized, then it wont come up in the for range loop. For each key, you can then start a goroutine that listens so it doesnt block, or you can use a buffered channels so they wont block up to the buffer limit. You can also preferably use a waitGroup, rather than the time.Sleep(), these are only for this trivial example.
package main
import (
"fmt"
"time"
)
func main() {
m := make(map[int]chan string)
go func() {
m[0] = make(chan string)
m[0] <- "abab"
}()
time.Sleep(time.Second * 1) //sleep so the above goroutine initializes the key 0 channel
for key := range m{ //loop on all non-nil keys
fmt.Println(key)
go func(k int){ // goroutine to listen on this channel
fmt.Println(<- m[k])
}(key)
}
time.Sleep(time.Second * 1) //sleep so u can see the effects of the channel recievers
}
Old answer
This is how the flow is. The main goroutine starts. The map is created. The main goroutine encounters another goroutine. It spawns said goroutine and goes on with its life. Then it meets this line, fmt.Println(<-m[0])
, which is a problem, since the map is indeed initialized, but the channel in the map itself isnt initialized! By the time the main goroutine has reached fmt.Println(<-m[0])
, the other goroutine hadn't yet initialized the channel! So its a simple fix, just initialize the channel before spawning the goroutine and you're good to go!
package main
import "fmt"
func main() {
m := make(map[int]chan string)
m[0] = make(chan string)
go func() {
m[0] <- "abab"
}()
fmt.Println(<-m[0])
}
Edit: Note that fmt.Println(<-m[0])
is blocking, which means that if in that other goroutine, you dont send on the channel, you will also go into a deadlock, since you are trying to recieve on the channel when no one is actually sending.
CodePudding user response:
You need to synchronize the creation of a channel.
As it stands, your main thread arrives at <-m[0]
while m[0]
is still an uninitialized channel, and receiving on an uninitialized channel blocks forever.
Your go routine creates a new channel and places it in m[0]
, but the main go routine is already listening on the prior zero value. Sending on this new channel also blocks forever, as there is nothing reading from it, so all go routines block.
To fix this, move m[0] = make(chan string)
above your go routine, so it happens synchronously.