Understanding Goroutines & Channels with Ping Pong game

Deepankar Bhade /

4 min read--

A lesser-known fact about me is that I am a State level Table tennis player, which I played for a good 5 years. Another fun fact: Table tennis in Chinese is called "Ping Pong". But we aren't here for this BS.

"Ping Pong" in reference to networking, is communication where ping is a transmitted packet to a destination computer, and the pong is the response. In this blog, we will implement this ping-pong using goroutines and channels in golang.

Let's understand Goroutines & channels briefly

Goroutines

If we run the following code we would only see the "First" function running and the code for "Second" never executes. How do we make the first function run asynchronously?

main.go
package main import ( "fmt" "time" ) func loop(msg string) { for { fmt.Println(msg) time.Sleep(time.Second) } } func main() { loop("First") loop("Second") }

Goroutines helps you create a new thread for execution and will execute concurrently upon calling. Now if we run this code we would see both of our functions running simultaneously.

main.go
package main import ( "fmt" "time" ) func loop(msg string) { for { fmt.Println(msg) time.Sleep(time.Second) } } func main() {
go loop("First")
loop("Second") }

Channels

Channels are the pipes that connect concurrent goroutines. You can send values into channels from one goroutine and receive those values into another goroutine. In this simple example, we create a new channel send value to the channel from a goroutine, and receive it in the main goroutine.

main.go
package main import "fmt" func main() { // creating a new channel messages := make(chan string) // sending a value into a channel go func() { messages <- "ping" }() // receiving value from the channel msg := <-messages fmt.Println(msg) }

In an ideal game of table tennis "Player A" hits the ball and "Player B" on receiving hits the ball back to "Player A".

Now let's think that Player A sends a ping and Player B sends a pong back and the ball is our packet. In an ideal table tennis match Player B would always wait for the ball to come i.e. ping and respond with pong. Player A then similarly waits for pong and sends back Ping again.

Each player has its own goroutine since they are playing asynchronously. But in an ideal game, there still would be some sync between the players. For example, Player A needs to hit back only if Player B has returned the ball. To maintain this communication we create channels to communicate between the goroutines (players).

Player A would be "Pinger" its main job would be to wait for Ping and upon receiving send back a pong & Player B would be "Ponger" would wait for pong and upon receiving send back a ping. These ping & pong would be the channels that would help us connect our concurrent goroutines.

Here's an implementation of pinger and ponger, both of these handle ping, pong channels as explained above.

main.go
/* Would receive from ping channel Wait for 1 second Then send to pong channel */ func pinger(ping <-chan string, pong chan<- string) { for m := range ping { printAndDelay(m) pong <- "pong" } } /* Would receive from pong channel Wait for 1 second Then send to ping channel */ func ponger(ping chan<- string, pong <-chan string) { for m := range pong { printAndDelay(m) ping <- "ping" } }

Now to start the game we need to run both pinger & ponger concurrently with the help of goroutines. We will also need to start the game by sending a ping.

main.go
package main import ( "fmt" "time" ) /* Would receive from ping channel Wait for 1 second Then send to pong channel */ func pinger(ping <-chan string, pong chan<- string) { for m := range ping { printAndDelay(m) pong <- "pong" } } /* Would receive from pong channel Wait for 1 second Then send to ping channel */ func ponger(ping chan<- string, pong <-chan string) { for m := range pong { printAndDelay(m) ping <- "ping" } } func printAndDelay(msg string) { fmt.Println(msg) time.Sleep(time.Second) } func main() { ping := make(chan string) pong := make(chan string) // Player A go pinger(ping, pong) // Player B go ponger(ping, pong) // Player A starts the game ping <- "ping" for { } }
Ping Pong in action

Ping Pong in action

Hope you’ve learned something new, thanks for reading!

Note

A quick favor: was anything I wrote incorrectly or misspelled, or do you still have questions? Feel free to message me on twitter .


Read more: