为什么这段Golang代码会产生死锁呢?

Why does this Golang code produce deadlock?

我是 Golang 的新手,我很难弄清楚为什么以下代码会产生死锁。另外,我该如何修复它才能正常工作?

    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])
    }

编辑:

感谢您的回答!不幸的是,用

初始化 m[0]
m[0] = make(chan string)

在启动一个新的 goroutine 之前并不是我想要的。我的问题是:有没有办法“动态”创建频道?例如。我有一个 map[int]chan string 类型的映射 m,我收到的请求包含 id 类型 int 之类的内容。我想通过频道 map[id] 发送消息,但为每个 int 初始化频道的成本太高。我如何 solve/work 解决这个问题?

所以,换句话说,我希望每个 id 都有一个单独的作业队列,并懒惰地初始化每个队列。

OP 更新问题后更新答案

你可以只循环映射中的所有键,也许有另一个 goroutine 不断循环所有键。显然,如果一个键没有被初始化,那么它就不会出现在 for range 循环中。对于每个键,您可以启动一个 goroutine 来监听,这样它就不会阻塞,或者您可以使用缓冲通道,这样它们就不会阻塞到缓冲区限制。您也可以最好使用 waitGroup,而不是 time.Sleep(),这些仅适用于这个简单的示例。

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


}
  

旧答案

流程是这样的。主 goroutine 启动。地图已创建。主 goroutine 遇到另一个 goroutine。它产生了所说的 goroutine 并继续它的生命。然后它遇到了这一行,fmt.Println(<-m[0]),这是一个问题,因为地图确实被初始化了,但是地图本身的通道没有被初始化!当主 goroutine 到达 fmt.Println(<-m[0]) 时,另一个 goroutine 还没有初始化通道!所以这是一个简单的修复,只需在生成 goroutine 之前初始化通道就可以了!

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])
}

编辑:注意 fmt.Println(<-m[0]) 是阻塞的,这意味着如果在另一个 goroutine 中,你不在通道上发送,你也会陷入僵局,因为你试图在通道上接收当实际上没有人发送时。

您需要同步创建频道。

就目前而言,您的主线程到达 <-m[0]m[0] 仍然是未初始化的通道,并且在未初始化的通道上接收永远阻塞。

您的 go 例程创建了一个新通道并将其放置在 m[0] 中,但是主要的 go 例程已经在监听先前的零值。在这个新频道上发送也永远阻塞,因为没有从它读取任何东西,所以所有的例程都阻塞。

要解决此问题,请将 m[0] = make(chan string) 移动到您的 go 例程之上,以便它同步发生。