英文:
Get deadlock when try to received value twice
问题
我看了关于高级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) // 游戏开始;抛出球
time.Sleep(1 * time.Second)
fmt.Println(<-table) // 游戏结束;抓住球
}
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) // 游戏开始;抛出球
time.Sleep(1 * time.Second)
fmt.Println(<-table) // 游戏结束;抓住球
fmt.Println(<-table) // 游戏结束;抓住球
}
func player(name string, table chan *Ball) {
for {
ball := <-table
ball.hits++
fmt.Println(name, ball.hits)
time.Sleep(100 * time.Millisecond)
table <- ball
}
}
我在这里遇到了一个死锁,真的不明白为什么。看一下goroutine中的最后一行,在后台,两个goroutine仍然继续循环并相互发送值。对我来说,似乎是对table变量通道的多个接收者。
我的主要问题是,为什么第二个示例中会发生死锁?
英文:
I watch the awesome video about Advanced Go Concurrency Patterns. At the beginning Sameer Ajmani shows a ping pong application.
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
}
}
The code how it works, I understand to 90 percent. They are two goroutines they send to each other messages, ping and pong, during the main thread sleeps.
Then I try following
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
}
}
I've got here a deadlock and really do not understand why. Look at the last line in the go routine, I try to receive value from channel like the second last line. In background the two goroutines still continue loop and send to each other value. It seems to be for me a multiple receiver for table variable channel.
My main question is, what I've got by the second sample a deadlock?
答案1
得分: 3
在后台,两个goroutine仍然继续循环并相互发送值。
不,它们不会这样做。
当你使用make(chan *Ball)
创建通道时,你创建了一个无缓冲通道。这相当于说make(chan *Ball, 0)
,这意味着通道中可以容纳0个元素 - 或者更明确地说,对通道的写入将被阻塞,直到另一个例程从通道中读取,反之亦然。
无缓冲通道的执行顺序如下:
-
创建"ping"选手,尝试从
table
中读取ball := <-table
,被阻塞,直到table
被写入 -
创建"pong"选手,尝试从
table
中读取ball := <-table
,被阻塞,直到table
被写入 -
主线程使用以下代码将球写入
table
:table <- new(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) // 游戏结束;抓住球
此后,table
通道将为空,任何进一步对它的读取都将被阻塞。
- 此时,两个
player
例程都在ball := <-table
处被阻塞。
如果在主线程中进行进一步的<-table
读取,那个读取也将被阻塞,直到有例程尝试向table
通道写入。然而,由于没有其他例程在运行,你将会遇到死锁(所有goroutine都被阻塞)。
好的。那我能把两个球放在通道里吗?
不行。
如果我们尝试将两个球放入通道,我们可能会得到以下输出:
Ping 1
Pong 1
因为两个player
例程都将被卡在尝试将球放回通道的位置上 - 但没有人尝试读取它。执行顺序如下:
- 创建"ping"选手,尝试从
table
中读取,被阻塞 - 创建"pong"选手,尝试从
table
中读取,被阻塞 - 主线程将第一个球放入
table
(不会被阻塞,因为有人在等待读取) - 主线程将第二个球放入
table
(不会被阻塞,因为有人在等待读取) - "ping"读取第一个球
- "pong"读取第二个球
- "ping"将第一个球放在桌子上,被阻塞,因为没有人在等待读取
- "pong"将第二个球放在桌子上,被阻塞,因为没有人在等待读取
- 两个选手都被阻塞
直到主线程通过读取两个球来结束游戏。
正如一位评论者指出的,结束游戏的更好方法是关闭通道。但是,我希望这次讨论能够解决你的困惑。
英文:
> In background the two goroutines still continue loop and send to each other value.
No, they don't.
When you make the channel with make(chan *Ball)
, you are making an unbuffered channel. This is equivalent to saying make(chan *Ball,0)
, which means that the channel can fit 0 things in it - or, more explicitly, any writes to the channel will block until another routine reads from the channel, and vice versa.
The order of execution with an unbuffered channel is this:
-
Player "ping" created, tries to read from table with
ball := <-table
, blocked untiltable
is written to -
Player "pong" created, tries to read from table with
ball := <-table
, blocked untiltable
is written to -
Main writes the Ball to
table
with the following line:table <- new(Ball) // game on; toss the ball
This is not blocked because someone is waiting to read on the channel.
- Now ping reads the ball (execution continues after ping's
ball := <-table
) - Ping puts the ball on the table with
table <- ball
, not blocked because pong is waiting - Pong reads the ball (execution continues after pong's
ball := <-table
) - Pong puts the ball on the table with
table <- ball
, not blocked because ping is waiting - Ping reads the ball (execution continues after ping's
ball := <-table
) - ....etc
- Until main ends the game by reading the ball instead of one of the players
This is a great example of using a channel to ensure that only one routine is running at once.
To end the game, the main thread simply snatches the ball out of the channel after one second:
time.Sleep(1 * time.Second)
fmt.Println(<-table) // game over; grab the ball
After this, the table
channel will be empty, and any further reads on it will block.
- At this point, both
player
routines are blocked atball := <- table
.
If you do a further <-table
read in the main thread, that read will also block, until a routine tries to write to the table channel. However, since no other routines are running, you have a deadlock (all goroutines are blocked).
Ok. Can I just put two balls in the channel then?
No.
If we try to put two balls in the channel, we'll probably get the output:
Ping 1
Pong 1
because both player
routines will be stuck trying to put their ball back into the channel - but nobody will be trying to read it. The order looks like this:
- Player "ping" created, tries to read from table, blocked
- Player "pong" created, tries to read from table, blocked
- main puts the first ball into
table
(not blocked because someone is waiting to read) - main puts the second ball into
table
(not blocked because someone is waiting to read) - Ping reads the first ball
- Pong reads the second ball
- Ping puts the first ball on the table, blocked because nobody is waiting to read
- Pong puts the second ball on the table, blocked because nobody is waiting to read
- Both players are blocked
- Until main ends the game by reading both balls.
Here's a program to illustrate
As a commenter points out, a better thing to do to end the game would be to close the channel. But, I hope this discussion has cleared up your confusion.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论