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

huangapple go评论78阅读模式
英文:

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

编辑:

谢谢你的回答!不幸的是,在启动新的goroutine之前用以下方式初始化m[0]

m[0] = make(chan string)

并不完全是我想要的。我的问题是:是否有一种方法可以动态地创建通道?例如,我有一个类型为map[int]chan string的映射m,我收到的请求包含类似于int类型的id。我想通过通道map[id]发送消息,但为每个int初始化通道的成本太高了。我该如何解决或绕过这个问题?

换句话说,我想为每个id都有一个单独的作业队列,并且每个队列都是惰性初始化的。

英文:

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 &quot;fmt&quot;
    
    func main() {
    	m := make(map[int]chan string)
    	go func() {
    		m[0] = make(chan string)
    		m[0] &lt;- &quot;abab&quot;
    	}()
    	fmt.Println(&lt;-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.

答案1

得分: 2

在问题更新后更新的答案

你可以遍历映射中的所有键,也许可以有另一个 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) // 等待 1 秒,以便上面的 goroutine 初始化键 0 的通道

	for key := range m { // 循环遍历所有非 nil 的键
		fmt.Println(key)
		go func(k int) { // goroutine 来监听这个通道
			fmt.Println(<-m[k])
		}(key)
	}
	time.Sleep(time.Second * 1) // 等待 1 秒,以便你可以看到通道接收者的效果
}

旧答案

流程如下:主 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 中,你没有在通道上发送数据,你也会进入死锁状态,因为你在没有人实际发送数据的情况下尝试接收通道上的数据。

英文:

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 (
&quot;fmt&quot;
	&quot;time&quot;
)
func main() {
    m := make(map[int]chan string)

    go func() {
        m[0] = make(chan string)
        m[0] &lt;- &quot;abab&quot;
    }()

	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(&lt;- 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(&lt;-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(&lt;-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 &quot;fmt&quot;

func main() {
    m := make(map[int]chan string)
    m[0] = make(chan string)

    go func() {
        m[0] &lt;- &quot;abab&quot;
    }()
    fmt.Println(&lt;-m[0])
}

Edit: Note that fmt.Println(&lt;-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.

答案2

得分: 1

你需要同步创建一个通道。

目前情况下,你的主线程在 &lt;-m[0] 处到达,而 m[0] 仍然是一个未初始化的通道,接收未初始化的通道会永远阻塞。

你的 Go 协程创建了一个新的通道并将其放入 m[0],但是主 Go 协程已经在之前的零值上进行监听。在这个新通道上发送也会永远阻塞,因为没有任何地方读取它,所以所有的 Go 协程都会阻塞。

要解决这个问题,将 m[0] = make(chan string) 移动到你的 Go 协程上方,这样它就会同步发生。

英文:

You need to synchronize the creation of a channel.

As it stands, your main thread arrives at &lt;-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.

huangapple
  • 本文由 发表于 2021年10月25日 20:16:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/69707918.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定