Routines and Channels in Golang

April 01, 2020

Today we'll be going over how we can take Golang's synchonous nature and turn it asynchronous by splitting its main routine into child routines, and making use of the channels that get exposed from within.

But wait, what the heck is a routine?

When we execute a program in Go, we automatically create one Go routine. You can think of a routine as something that exists inside of the program; It executes each line one by one. But let's say we don't want this kind of behavior in our application, that's where we can instantiate new routines, otherwise known as child routines.

If you're familiar with Nodejs, creating new routines is essentially the same thing as using async / await in Node. Conceptually, both allow you to execute your code asynchronously without having to start any new threads.

Let's look at an example:

package main

import (
  "fmt"
  "net/http"
)
func main() {
links := []string{
"https://www.google.com"
"https://www.twitter.com"
"https://www.youtube.com"
}
for _, link := range links {
checkLinks(link)
}
}
func checkLinks(link string) {
_, err := http.get(link)
if err != nil {
fmt.Printf("%s might be down!\n", link)
return
}

fmt.Printf("%s is online\n", link)

}

What's happening here is that when Go loops through the slice of links and inevitably executes the http request for the link, the main routine that gets created goes to sleep and waits until it gets a response. Then it keeps continuing until there aren't any links left, resulting in:


https://www.google.com is online!
https://www.twitter.com is online!
https://www.youtube.com is online!

As you can see, no matter how many times we run the program like this, it will always be completely synchronous.

So, how do we create child routines?

Fortunately, this is very simple to do in Go. All we have to do use make use of the go keyword. What this does behind the scenes is that when the child routine that's created by using the go keyword goes to sleep, control flow is then passed back to the main routine, and goes back through the loop. When it gets to the http request again, it'll spawn a new child routine and keep doing so until there are no more links left.

The syntax for implementing this is very straight forward as well:


package main

import (
  "fmt"
  "net/http"
)

func main() {
  links := []string{
    "https://www.google.com"
    "https://www.twitter.com"
    "https://www.youtube.com"
  }

  for _, link := range links {
    // add the go keyword before the function is invoked
    // and that's it!
    go checkLinks(link)
  }
}

func checkLinks(link string) {
  _, err := http.get(link)

  if err != nil {
    fmt.Printf("%s might be down!\n", link)
    return
  }

  fmt.Printf("%s is online\n", link)
}

But there's a problem, because when we run the program, nothing happens. This is because when the main routine creates every child routine, it looks through the rest of the main function and sees if there's anything left to do. Now in this particular case there isn't, so it just exits before the child routines can finish.

Communication between Go routines via channels

We can prevent this from happening by implementing another feature Go has called Channels. You can think of a channel as a way of communicating between every single routine. Here we'll use a channel to make sure that the main routine waits until the child routines are done.


package main

import (
  "fmt"
  "net/http"
)

func main() {
  links := []string{
    "https://www.google.com"
    "https://www.twitter.com"
    "https://www.youtube.com"
  }

  // initalize the channel and set its type
  // this tells the channel what type to use for communication
  c := make(chan string)

  for _, link := range links {

    // pass the channel in the function so it has access
    go checkLinks(link, c)
  }

  for i := 0; i < len(links); i++ {

    // print whatever gets passed in the channel for each link
    fmt.Println(<-c)
  }
}

// add the channel as an argument
func checkLinks(link string, c chan string) {
  _, err := http.get(link)

  if err != nil {
    fmt.Printf("%s might be down!\n", link)

    // pass the value into the channel
    c <- "Might be down I think"
    return
  }

  fmt.Printf("%s is online\n", link)
  c <- "Yup its up"
}

Now when we execute this program:


https://www.twitter.com is online!
Yup its up

https://www.youtube.com is online!
Yup its up

https://www.google.com is online!
Yup its up

And that's it! In a real world application things will surely be more convoluted than the example provided here, but I hope that you were able to get something out of this and obtain a better understanding of how routines and channels work in Golang, and what problems they solve.

If you want to read more about routines and channels you can do so here