这不是关于如何更好地写这个的问题.这是一个特别关于为什么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
}
}
Run Code Online (Sandbox Code Playgroud)
输出:
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
Run Code Online (Sandbox Code Playgroud)
为什么这不会导致无限循环?为什么在放弃之前它会完成两次完整的"ping-ping"(而不仅仅是一次)?
从运行时的角度来看,你会遇到死锁,因为所有例程都试图发送到一个通道,并且没有例程等待接收任何东西.
但为什么会这样呢?我会给你一个故事,因为我喜欢想象我遇到死锁时我的惯例在做什么.
你有两个球员(套路)和一个球(true值).每个玩家等待一个球,一旦他们得到它,他们会将其传递给另一个玩家(通过一个频道).这是你的两个例程真正做的事情,这确实会产生无限循环.
问题是你的主循环中引入的第三个玩家.他躲在第二个球员后面,一旦他看到第一个球员空手,他就会向他投掷另一个球.所以我们最终让两名球员拿球,不能将球传给另一名球员,因为另一名球员已经掌握了(第一个)球.隐藏的,邪恶的球员也试图通过另一个球.每个人都很困惑,因为有三个球,三个球员,没有空手.
换句话说,你已经介绍了第三个破坏游戏的玩家.他应该是一个仲裁者,在比赛开始的时候传递第一个球,看着它,但是停止生产球!这意味着,而不是在你的主程序中有一个循环,应该是简单的chan1 <- true(和一些条件等待,所以我们不退出程序).
如果在主例程循环中启用日志记录,您将看到在第三次迭代时始终发生死锁.执行其他例程的次数取决于调度程序.带回故事:第一次迭代是第一球的开球; 下一次迭代是一个神秘的第二球,但这可以处理.第三次迭代是一个僵局 - 它使第三个球无法被任何人处理.