为啥下面的代码会产生死锁
Posted
技术标签:
【中文标题】为啥下面的代码会产生死锁【英文标题】:Why following code generates deadlock为什么下面的代码会产生死锁 【发布时间】:2017-07-09 06:00:43 【问题描述】:Golang 新手在这里。有人能解释一下为什么下面的代码会产生死锁吗?
我知道将 true 发送到 boolean
package main
import (
"fmt"
"sync"
"time"
)
var wg2 sync.WaitGroup
func producer2(c chan<- int)
for i := 0; i < 5; i++
time.Sleep(time.Second * 10)
fmt.Println("Producer Writing to chan %d", i)
c <- i
func consumer2(c <-chan int)
defer wg2.Done()
fmt.Println("Consumer Got value %d", <-c)
func main()
c := make(chan int)
wg2.Add(5)
fmt.Println("Starting .... 1")
go producer2(c)
go consumer2(c)
fmt.Println("Starting .... 2")
wg2.Wait()
以下是我的理解,我知道这是错误的:
-
通道将在写入 0 时被阻塞
生产者函数循环
所以我希望通道被
之后的消费者。
由于通道在步骤 2 中被清空,
生产者函数可以再次输入另一个值然后得到
被阻止并再次重复第 2 步。
【问题讨论】:
您向频道发送了五次消息,但只使用了一次频道。 producer2 goroutine 将被阻塞,直到四个消息的其余部分被消费。 另外,您对sync.WaitGroup
的使用是错误的,因为它调用了Add(5)
,但只调用了一次Done()
。
@ymonad 我补充了我的理解。你能纠正我哪里出错了吗?注册。 Add(5) - 当消费者执行 5 次时,我期望 Done() 被调用 5 次
在第 3 步中,您提到第 2 步会再次重复,但事实并非如此,您的代码中的消费者只消费一次。
【参考方案1】:
你原来的死锁是由wg2.Add(5)
引起的,你在等待5个goroutines完成,但只有一个完成;你打过一次wg2.Done()
。把它改成wg2.Add(1)
,你的程序就可以正常运行了。
但是,我怀疑您打算使用通道中的所有值,而不仅仅是像您所做的那样。如果您将消费者功能更改为:
func consumer2(c <-chan int)
defer wg2.Done()
for i := range c
fmt.Printf("Consumer Got value %d\n", i)
你会遇到另一个死锁,因为通道没有在生产者函数中关闭,而消费者正在等待更多永远不会到达的值。将close(c)
添加到生产者函数将修复它。
【讨论】:
【参考方案2】:为什么会出错?
运行您的代码得到以下错误:
➜ gochannel go run dl.go
Starting .... 1
Starting .... 2
Producer Writing to chan 0
Consumer Got value 0
Producer Writing to chan 1
fatal error: all goroutines are asleep - deadlock!
原因如下:
您的代码中有三个 goroutine:main
、producer2
和 consumer2
。运行时,
producer2
向频道发送号码0
consumer2
从频道接收 0
,然后退出
producer2
将1
发送到频道,但没有人在消费,因为consumer2
已经退出
producer2
正在等待
main
执行 wg2.Wait()
,但并非所有等待组都已关闭。所以 main 正在等待
有两个goroutines在这里等待,什么都不做,不管你等多久都不会做任何事情。 这是一个死锁! Golang 检测到它并恐慌。
您在这里混淆了两个概念:
-
waitgourp 的工作原理
如何从通道接收所有值
我在这里简单解释一下,网上已经有很多文章了。
等待组的工作原理
WaitGroup
如果有一种方法可以等待所有 groutine 完成。在后台运行 goroutine 时,重要的是要知道它们何时全部退出,然后才能执行某些操作。
在你的例子中,我们运行了两个goroutine,所以一开始我们应该设置wg2.Add(2)
,每个goroutine应该添加wg2.Done()
来通知它已经完成。
从通道接收数据
从通道接收数据时。如果您确切知道它将发送多少数据,请以这种方式使用for
循环:
for i:=0; i<N; i++
data = <-c
process(data)
否则这样使用:
for data := range c
process(data)
另外,不要忘记在没有更多数据发送时关闭通道。
如何解决?
通过上面的解释,代码可以固定为:
package main
import (
"fmt"
"sync"
"time"
)
var wg2 sync.WaitGroup
func producer2(c chan<- int)
defer wg2.Done()
for i := 0; i < 5; i++
time.Sleep(time.Second * 1)
fmt.Printf("Producer Writing to chan %d\n", i)
c <- i
close(c)
func consumer2(c <-chan int)
defer wg2.Done()
for i := range c
fmt.Printf("Consumer Got value %d\n", i)
func main()
c := make(chan int)
wg2.Add(2)
fmt.Println("Starting .... 1")
go producer2(c)
go consumer2(c)
fmt.Println("Starting .... 2")
wg2.Wait()
Here 是另一种可能的修复方法。
预期输出
固定代码给出以下输出:
➜ gochannel go run dl.go
Starting .... 1
Starting .... 2
Producer Writing to chan 0
Consumer Got value 0
Producer Writing to chan 1
Consumer Got value 1
Producer Writing to chan 2
Consumer Got value 2
Producer Writing to chan 3
Consumer Got value 3
Producer Writing to chan 4
Consumer Got value 4
【讨论】:
以上是关于为啥下面的代码会产生死锁的主要内容,如果未能解决你的问题,请参考以下文章