DEV Community

Cover image for Understanding Goroutines & Channels with Ping Pong game
Deepankar Bhade
Deepankar Bhade

Posted on • Originally published at dpnkr.in

Understanding Goroutines & Channels with Ping Pong game

#go

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?

package main

import (
  "fmt"
  "time"
)

func loop(msg string) {
  for {
    fmt.Println(msg)
    time.Sleep(time.Second)
  }
}

func main() {
  loop("First")
  loop("Second")
}
Enter fullscreen mode Exit fullscreen mode

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.

package main

import (
  "fmt"
  "time"
)

func loop(msg string) {
  for {
    fmt.Println(msg)
    time.Sleep(time.Second)
  }
}

func main() {
  go loop("First")
  loop("Second")
}
Enter fullscreen mode Exit fullscreen mode

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.

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)
}
Enter fullscreen mode Exit fullscreen mode

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

Ping Pong

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.

Ping Pong

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.

/*
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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

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 {
  }
}
Enter fullscreen mode Exit fullscreen mode

Demo

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

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

Discussion (3)

Collapse
ernestvonmoscow profile image
VanPonasenkov

I think that this is a great explanation of goroutines and channels for beginners. Great Job!

Collapse
deepcodes profile image
Deepankar Bhade Author

Thanks means a lot to me :)