我看了关于 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%。它们是两个 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 例程中的最后一行,我尝试像倒数第二行一样从 channel 接收值。在后台,两个 goroutines 仍然继续循环并互相发送值。对我来说,它似乎是一个用于表变量 channel 的多接收器。
我的主要问题是,我在第二个样本中遇到了什么僵局?
最佳答案
In background the two goroutines still continue loop and send to each other value.
不,他们没有。
当您使用 make(chan *Ball)
创建 channel 时,你正在制作一个无缓冲的 channel 。这相当于说 make(chan *Ball,0)
,这意味着该 channel 可以容纳 0 个东西 - 或者,更明确地说,对 channel 的任何写入都将阻塞,直到另一个例程从该 channel 读取,反之亦然。
无缓冲 channel 的执行顺序是这样的:
- 玩家“ping”已创建,尝试从
ball := <-table
的表中读取, 直到table
才被阻止写给 - 玩家“pong”已创建,尝试使用
ball := <-table
从表中读取, 直到table
才被阻止写给 Main 将 Ball 写入
table
使用以下行:table <- new(Ball) // game on; toss the ball
这没有被屏蔽,因为有人在 channel 上等待阅读。
- 现在 ping 读取球(在 ping 的
ball := <-table
之后继续执行) - Ping 用
table <- ball
把球放在 table 上, 未被阻止,因为 pong 正在等待 - Pong 读球(在 pong 的
ball := <-table
之后继续执行) - Pong 用
table <- ball
把球放在 table 上, 未被阻止,因为 ping 正在等待 - Ping 读取球(在 ping 的
ball := <-table
之后继续执行) - ....等
- 直到 main 通过阅读球而不是其中一名球员结束比赛
这是使用 channel 确保一次只有一个例程运行的一个很好的例子。
为了结束游戏,主线程在一秒后将球从 channel 中抢走:
time.Sleep(1 * time.Second)
fmt.Println(<-table) // game over; grab the ball
在此之后,table
channel 将是空的,任何进一步的读取都将被阻塞。
- 此时,
player
例程在ball := <- table
处被阻止.
如果你再做一次<-table
在主线程中读取, 读取也会阻塞,直到例程尝试写入表 channel 。但是,由于没有其他例程在运行,因此出现死锁(所有 goroutine 都被阻塞)。
好的。那我可以把两个球放在 channel 里吗?
没有。
如果我们尝试将两个球放入 channel 中,我们可能会得到输出:
Ping 1
Pong 1
因为两者都是player
试图将他们的球放回 channel 中的例程将被卡住 - 但没有人会试图阅读它。订单看起来像这样:
- 播放器“ping”已创建,尝试从表中读取,已阻止
- 玩家“pong”已创建,尝试从表中读取,已被阻止
- main 将第一个球放入
table
(没有因为有人在等待阅读而被阻止) - main 将第二个球放入
table
(没有因为有人在等待阅读而被阻止) - 平读第一个球
- Pong读第二个球
- Ping 将第一个球放在 table 上,因为没有人在等待阅读而被挡住了
- Pong 将第二个球放在 table 上,因为没有人等着看而被挡住了
- 两个玩家都被阻止了
- 直到 main 通过读取两个球结束游戏。
Here's a program to illustrate
正如评论者所指出的,结束游戏的更好方法是关闭 channel 。但是,我希望这个讨论已经消除了您的困惑。
关于multithreading - 尝试两次接收值时出现死锁,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26009501/