【问题标题】:Why does this Golang code produce deadlock?为什么这段 Golang 代码会产生死锁?
【发布时间】:2021-12-10 22:19:50
【问题描述】:

我是 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 初始化频道成本太高。我该如何解决/解决这个问题?

因此,换句话说,我想为每个id 建立一个单独的作业队列,并懒惰地初始化每个队列。

【问题讨论】:

  • 您认为语句的执行顺序是什么?提示:在你的&lt;-m[0] 之前添加time.Sleep(time.Second) 可以修复你的程序(错误的方式)。
  • @mkopriva 好的,谢谢!有没有办法“动态”创建频道?例如。我有一张m 类型为map[int]chan string 的地图,我收到包含id 类型int 之类的请求。我想通过频道map[id] 发送消息,但是为每个int 初始化频道成本太高。我该如何解决/解决这个问题?

标签: go concurrency deadlock


【解决方案1】:

OP 更新问题后更新答案

你可以只循环 map 中的所有键,也许还有另一个 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(&lt;-m[0])这行,这是个问题,因为map确实初始化了,但是map本身的channel没有初始化!当主 goroutine 到达 fmt.Println(&lt;-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(&lt;-m[0]) 是阻塞的,这意味着如果在另一个 goroutine 中,你不在通道上发送,你也会陷入死锁,因为你试图在没有人的情况下在通道上接收实际发送。

【讨论】:

  • 谢谢!你能看看更新的问题吗?
  • @GinGin3203,更新了我对你问题的回答,希望解释清楚
【解决方案2】:

您需要同步频道的创建。

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-06-20
    • 1970-01-01
    • 1970-01-01
    • 2012-08-27
    • 1970-01-01
    • 2017-07-09
    • 2020-08-20
    • 1970-01-01
    相关资源
    最近更新 更多