这不是关于如何更好地写这个的问题。这是一个特别关于为什么Go在这种情况下导致死锁的问题。
package main
import "fmt"
func main() {
chan1 := make(chan bool)
chan2 := make(chan bool)
go func() {
for {
<-chan1
fmt.Printf("chan1\n")
chan2 <- true
}
}()
go func() {
for {
<-chan2
fmt.Printf("chan2\n")
chan1 <- true
}
}()
for {
chan1 <- true
}
}
输出:
chan1
chan2
chan1
chan2
chan1
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
goroutine 5 [chan send]:
goroutine 6 [chan send]:
exit status 2
为什么这不会导致无限循环?怎么会有两个完整的&#34; ping-ping&#34;放弃之前(而不仅仅是一个)?
答案 0 :(得分:7)
从运行时的角度来看,你会遇到一个死锁,因为所有的例程都试图发送到一个频道,并且没有例程等待接收任何东西。
但为什么会发生什么?我会给你一个故事,因为我喜欢想象我遇到死锁时我的惯例在做什么。
你有两个球员(套路)和一个球(true
值)。每个玩家等待一个球,一旦他们得到它,他们会将其传递给另一个玩家(通过一个频道)。这就是你的两个例程正在做的事情,这确实会产生无限循环。
问题是你的主循环中引入的第三个玩家。他躲在第二个球员后面,一旦他看到第一个球员空手,他就会向他投掷另一个球。所以我们最终得到两个球员拿球,无法将球传给另一个球员,因为另一个球已经掌握了(第一个)球。隐藏的,邪恶的玩家也试图传递又一个球。每个人都很困惑,因为有三个球,三个球员,没有空手。
换句话说,你已经介绍了第三个打破游戏的玩家。他应该是一个仲裁者,在比赛开始的时候传递第一个球,看着它,但是停止生产球!这意味着,而不是在主程序中有一个循环,应该只是chan1 <- true
(和某些条件要等待,所以我们不要退出程序)。
如果在主例程循环中启用日志记录,您将在第三次迭代中看到始终的死锁。执行其他例程的次数取决于调度程序。带回故事:第一次迭代是第一球的开球;下一次迭代是一个神秘的第二球,但这可以处理。第三次迭代是一个僵局 - 它使第三个球无法被任何人处理。
答案 1 :(得分:1)
goroutine 1 [chan send]:
goroutine 5 [chan send]:
goroutine 6 [chan send]:
这说明了一切:所有的goroutine都被阻止,试图发送一个没有人在另一端接收的频道。
所以你的chan2 <- true
上的第一个goroutine阻止,chan1 <- true
上的第二个阻挡以及你自己的主要goroutine阻挡chan1 <- true
。
至于为什么它会完成两次&#34;完全ping-pings&#34;就像你说的那样,它取决于日程安排以及发件人<-chan1
首先决定接收的日期。
在我的电脑上,我得到更多,每次运行时都会有所不同:
chan1
chan2
chan1
chan2
chan1
chan2
chan1
chan2
chan1
chan2
chan1
chan2
chan1
fatal error: all goroutines are asleep - deadlock!
答案 2 :(得分:1)
看起来很复杂,但答案很简单。
在以下情况下会陷入僵局:
chan2
chan1
。chan1
。怎么会发生这种情况?例如:
chan1
。阻止另一个写。chan1
从Main收到。打印。阻止写chan2
。chan2
收到。打印。阻止写chan1
。chan1
从例行程序2收到。打印。阻止写chan2
。chan2
收到。打印。阻止写chan1
。chan1
。阻止另一个写。chan1
从Main收到。打印。阻止写chan2
。chan1
。阻止另一个写。目前所有例程都被阻止。即:
例程1无法写入chan2
,因为例程2未接收但实际上阻止尝试写入chan1
。但没人在听chan1
。
正如@HectorJ所说,这一切都取决于调度程序。但在这种设置中,死锁是不可避免的。