为什么这会导致Go陷入僵局?

时间:2015-11-17 12:35:48

标签: go deadlock

这不是关于如何更好地写这个的问题。这是一个特别关于为什么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;放弃之前(而不仅仅是一个)?

3 个答案:

答案 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。阻止另一个写。
  • 例程1:chan1从Main收到。打印。阻止写chan2
  • 例程2:chan2收到。打印。阻止写chan1
  • 例行程序1:chan1从例行程序2收到。打印。阻止写chan2
  • 例程2:chan2收到。打印。阻止写chan1
  • 主要写chan1。阻止另一个写。
  • 例程1:chan1从Main收到。打印。阻止写chan2
  • 主要写chan1。阻止另一个写。

目前所有例程都被阻止。即:

例程1无法写入chan2,因为例程2未接收但实际上阻止尝试写入chan1。但没人在听chan1

正如@HectorJ所说,这一切都取决于调度程序。但在这种设置中,死锁是不可避免的。