为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
慕哥9229398 2021-08-16 19:47:27
我观看了关于高级 Go 并发模式的精彩视频。一开始,Sameer Ajmani 展示了一个乒乓应用程序。package mainimport (    "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%。它们是两个 goroutine,它们在主线程休眠期间相互发送消息,ping 和 pong。然后我尝试跟随package mainimport (    "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 回答

?
蛊毒传说

TA贡献1895条经验 获得超3个赞

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

不,他们没有。

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

无缓冲通道的执行顺序是这样的:

  • 玩家“ping”创建,尝试从表中读取 ball := <-table,阻塞直到table被写入

  • 玩家“pong”创建,尝试从表中读取ball := <-table,阻塞直到table被写入

  • Maintable使用以下行将Ball 写入:

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

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

  • 现在 ping 读取球(在 ping 之后继续执行ball := <-table

  • Ping 把球放在桌子上table <- ball,没有被挡住,因为 pong 正在等待

  • Pong 读球(在 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 1Pong 1

因为这两个player例程都会在试图将球放回通道时被卡住 - 但没有人会试图阅读它。订单如下所示:

  • 玩家“ping”创建,尝试从表中读取,被阻止

  • 玩家“pong”创建,尝试从表中读取,被阻止

  • main 将第一个球放入table(未阻塞,因为有人在等待阅读)

  • main 将第二个球放入table(没有被阻塞,因为有人在等待阅读)

  • 平读第一个球

  • 庞读第二个球

  • Ping 把第一个球放在桌子上,因为没有人在等待阅读而被阻止

  • Pong 把第二个球放在桌子上,因为没人等着看,所以被挡住了

  • 两名球员都被封锁

    • 直到主要通过读取两个球来结束游戏。

这是一个程序来说明

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


查看完整回答
反对 回复 2021-08-16
  • 1 回答
  • 0 关注
  • 171 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信