猿问

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

我观看了关于高级 Go 并发模式的精彩视频。一开始,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%。它们是两个 goroutine,它们在主线程休眠期间相互发送消息,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 仍然继续循环并相互发送值。对我来说,它似乎是表变量通道的多个接收器。


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


慕哥9229398
浏览 172回答 1
1回答

蛊毒传说

在后台,两个 goroutine 仍然继续循环并相互发送值。不,他们没有。当您使用 制作频道时make(chan *Ball),您正在制作一个无缓冲的频道。这相当于说make(chan *Ball,0),这意味着通道可以在其中容纳 0 个东西 - 或者更明确地说,对通道的任何写入都将阻塞,直到另一个例程从通道读取,反之亦然。无缓冲通道的执行顺序是这样的:玩家“ping”创建,尝试从表中读取&nbsp;ball := <-table,阻塞直到table被写入玩家“pong”创建,尝试从表中读取ball := <-table,阻塞直到table被写入Maintable使用以下行将Ball 写入:&nbsp;table&nbsp;<-&nbsp;new(Ball)&nbsp;//&nbsp;game&nbsp;on;&nbsp;toss&nbsp;the&nbsp;ball这不会被阻止,因为有人正在等待在频道上阅读。现在 ping 读取球(在 ping 之后继续执行ball := <-table)Ping 把球放在桌子上table <- ball,没有被挡住,因为 pong 正在等待Pong 读球(在 pong 之后继续执行ball := <-table)Pong 把球放在桌子上table <- ball,没有被挡住,因为 ping 正在等待ping 读球(ping 后继续执行ball := <-table)....等等直到主要通过阅读球而不是其中一名球员来结束比赛这是使用通道确保一次只运行一个例程的一个很好的示例。为了结束游戏,主线程在一秒钟后简单地将球抢出通道:&nbsp;time.Sleep(1&nbsp;*&nbsp;time.Second) &nbsp;fmt.Println(<-table)&nbsp;//&nbsp;game&nbsp;over;&nbsp;grab&nbsp;the&nbsp;ball在此之后,table通道将是空的,任何进一步的读取都会被阻塞。此时,两个player例程都被阻塞在ball := <- table。如果您<-table在主线程中进一步读取,该读取也将阻塞,直到例程尝试写入表通道。但是,由于没有其他例程在运行,因此会出现死锁(所有 goroutine 都被阻塞)。行。那么我可以在通道中放两个球吗?不。如果我们尝试在通道中放入两个球,我们可能会得到输出:Ping&nbsp;1Pong&nbsp;1因为这两个player例程都会在试图将球放回通道时被卡住 - 但没有人会试图阅读它。订单如下所示:玩家“ping”创建,尝试从表中读取,被阻止玩家“pong”创建,尝试从表中读取,被阻止main 将第一个球放入table(未阻塞,因为有人在等待阅读)main 将第二个球放入table(没有被阻塞,因为有人在等待阅读)平读第一个球庞读第二个球Ping 把第一个球放在桌子上,因为没有人在等待阅读而被阻止Pong 把第二个球放在桌子上,因为没人等着看,所以被挡住了两名球员都被封锁直到主要通过读取两个球来结束游戏。这是一个程序来说明正如评论者指出的那样,结束游戏的更好做法是关闭频道。但是,我希望这次讨论能消除您的困惑。
随时随地看视频慕课网APP

相关分类

Go
我要回答