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?
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:
ball := <-table
, blocked until table
is written toball := <-table
, blocked until table
is written toMain 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.
ball := <-table
)table <- ball
, not blocked because pong is waitingball := <-table
)table <- ball
, not blocked because ping is waitingball := <-table
)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.
player
routines are blocked at ball := <- 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:
table
(not blocked because someone is waiting to read)table
(not blocked because someone is waiting to read)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.