尝试两次接收值时出现死锁

时间:2014-09-24 05:52:06

标签: multithreading go

我观看了关于Advanced Go Concurrency Patterns的精彩视频。最初,Sameer Ajmani展示了一个乒乓球应用程序。

package main

import (
    "fmt"
    "time"
)

type Ball struct{ hits int }

func main() {
    table := make(chan *Ball)
    go player("ping", table)
    go player("pong", table)

    table <- new(Ball) // game on; toss the ball
    time.Sleep(1 * time.Second)
    fmt.Println(<-table) // game over; grab the ball
}

func player(name string, table chan *Ball) {
    for {
        ball := <-table
        ball.hits++
        fmt.Println(name, ball.hits)
        time.Sleep(100 * time.Millisecond)
        table <- ball
    }
}

代码如何工作,我理解为90%。它们是两个goroutines,它们在主线程休眠期间发送给对方的消息,ping和pong。

然后我尝试关注

package main

import (
    "fmt"
    "time"
)

type Ball struct{ hits int }

func main() {
    table := make(chan *Ball)
    go player("ping", table)
    go player("pong", table)

    table <- new(Ball) // game on; toss the ball
    time.Sleep(1 * time.Second)
    fmt.Println(<-table) // game over; grab the ball
    fmt.Println(<-table) // game over; grab the ball
}

func player(name string, table chan *Ball) {
    for {
        ball := <-table
        ball.hits++
        fmt.Println(name, ball.hits)
        time.Sleep(100 * time.Millisecond)
        table <- ball
    }
}

我遇到了僵局,真的不明白为什么。看看go例程中的最后一行,我尝试从第二行的第二行接收来自通道的值。在后台,两个goroutine仍然继续循环并相互发送值。对我来说似乎是一个表变量通道的多接收器。

我的主要问题是,第二个样本我遇到了什么僵局?

1 个答案:

答案 0 :(得分:3)

  

在后台,两个goroutine仍然继续循环并相互发送值。

不,他们没有。

当您使用make(chan *Ball)制作频道时,您正在制作无缓冲频道。这相当于说make(chan *Ball,0),这意味着通道可以容纳0个内容 - 或者更明确地说,对通道的任何写入都将阻塞,直到另一个例程从通道读取,反之亦然。

使用无缓冲通道执行的顺序是:

  • 玩家&#34; ping&#34;已创建,尝试从ball := <-table的表中读取,阻止直到table写入
  • Player&#34; pong&#34;已创建,尝试从ball := <-table的表中读取,阻止直到table写入
  • Main使用以下行将球写入table

     table <- new(Ball) // game on; toss the ball
    

    这不会被阻止,因为有人正在等待阅读该频道。

  • 现在ping读取球(在ping ball := <-table之后继续执行)
  • Ping将球放在桌子上table <- ball,未阻止,因为乒乓球正在等待
  • Pong读球(在乒乓球ball := <-table之后继续执行)
  • Pong将球放在table <- ball的桌面上,因为ping等待而未被阻止
  • Ping读球(在ping ball := <-table之后继续执行)
  • ....等
    • 直到主赛结束比赛,而不是其中一名球员

这是使用通道确保一次只运行一个例程的一个很好的例子。

要结束游戏,主线程只需在一秒钟后将球从通道中夺走:

 time.Sleep(1 * time.Second)
 fmt.Println(<-table) // game over; grab the ball

此后,table频道将为空,并且其上的任何进一步读取都将被阻止。

  • 此时,两个player例程都在ball := <- table被阻止。

如果您在主线程中进行了进一步的<-table读取,那么 读取也会阻塞,直到例程尝试写入表通道。但是,由于没有其他例程正在运行,因此会出现死锁(所有goroutine都被阻止)。


确定。我可以在通道中放两个球吗?

没有。

如果我们尝试在通道中放置两个球,我们可能会得到输出:

Ping 1
Pong 1

因为两个player例程都会被卡住,试图将他们的球放回频道 - 但是没有人会尝试阅读它。订单如下:

  • 玩家&#34; ping&#34;创建,尝试从表中读取,阻止
  • Player&#34; pong&#34;创建,尝试从表中读取,阻止
  • main将第一个球放入table(未被阻止,因为有人在等待阅读)
  • main将第二个球放入table(未被阻止,因为有人在等待阅读)
  • Ping读第一个球
  • Pong读第二球
  • Ping把第一个球放在桌子上,因为没有人等着看而被阻挡
  • Pong把第二个球放在桌子上,因为没人等着看而被阻挡
  • 两名球员都被封锁了
    • 通过阅读两个球直到主赛结束。

Here's a program to illustrate

正如评论者所指出的,结束游戏更好的办法是关闭频道。但是,我希望这次讨论能够消除你的困惑。