A first look at Goroutines

Stefan M.
Golicious
Published in
5 min readNov 12, 2020

Before I show you some code snippets, let me explain my current project, which issues I faced there, and how Goroutines fixed these.

I have developed a web application that makes an HTTP request to the App Store or to the Play Store or both. Depending on the input parameters. The application parses some HTML from the response and prints a few information afterward.

The application is developed pretty straightforward. It parses the input queries (which could be android and/or ios), put this information into two slices, and performs the HTTP requests one after another. First, all android inputs, followed by all ios inputs.

Based on the number of input parameters, this could take a lot of time before it prints something.

Assuming that each request to the stores will take 250 milliseconds for one parameter and given 4 parameters; the application will respond in 1 second before printing something. This is not a nice and especially not a scalable solution. Also because we use this application at work and is intended to use with at least 24 input parameters (12 for each platform).

The solution — Goroutines

To make my application response faster I have to execute some code in parallel, not sequential.

The first attempt was to parallelize the android and ios requests. This means all android inputs will be executed in parallel with all ios inputs. With that solution, I saved 50% of the response time. But that was not enough for me. So I dived deeper and executed each single HTTP request for each input in parallel as well!

The application behaves now:

  • Run android and ios requests in parallel
  • Execute each HTTP request for each platform in parallel
  • Wait until all requests are finished and print it

The output will be printed as fast as the slowest HTTP response. Regardless of the platform or store. If the slowest HTTP request takes 250 milliseconds, the application prints in 250 milliseconds. Furthermore, the response time of the application does not depend on the number of input parameters anymore. Now I’m able to put “unlimited” input parameters in without affecting the response time.

Now it is finally time to show you some code!

To get familiar with Goroutines I read the “A Tour of Go” concurrency chapter until lesson four. Which helped me quite well because the examples are basically exactly what I need.

In general, to start a new goroutine simply add the go word in front of your function call. The function will then, well, be executed in a new goroutine and therefore not block the current running code.

func main() {
go printSomething()
}
func printSomething() {
println("Print something")
}

If you run the example above, it will print nothing. This is intended because, as said above, the new goroutine does not block the current code. The program terminates before the println statement can be reached.

But how to wait until a function is finish with goroutines? I need this because in my application I had to wait until each android and ios slice finished their HTTP requests.

Go has a solution for that with so called channels. They can be used to send and receive values from another goroutine and therefore blocks until the other side is ready.

func main() {
doneChannel := make(chan bool)
go printSomething(doneChannel)
<-doneChannel
}
func printSomething(doneChannel chan bool) {
println("Print something")
doneChannel <- true
}

First, we have to create a channel with make(chan bool). This will make a channel of type boolean. But you can of course use any other type. In the printSomething function we send to the doneChannel a value. In this case true to mark the channel “as done”. The <-doneChannel syntax in the main function blocks until the channel received a value.

If we run this sample you would see the Print something output on your screen.

With that, I already parallelize my two slices. I created two functions, started both in a new goroutine and wait in main until both functions send the channel a value. But how to execute each HTTP request in parallel?

Well, in Go you can create inline functions. You can create a function inside another. With this technique it is possible to loop over an slice, create a new inline function which will run in another goroutine, and wait in the outer function until all of the started goroutines are finished.

import "fmt"func main() {
channel := make(chan int)
for i := 0; i < 3; i++ {
go func(iterable int) {
fmt.Printf("Called %d\n", iterable)
channel <- iterable
}(i)
}
for i := 0; i < 3; i++ {
<-channel
}
}

This time we make a channel of the type int. In the for loop we create an inline function and move it into a goroutine with the go keyword. If the “heavy” execution in that function is done, we will send the “terminal signal” to the channel (channel <- iterable).

Meanwhile, in the non blocking code, we start another for loop to wait exactly three times the channel received a value. Thus, we block the main function until the channel received three values.

You may ask what happens if we wait more times than the channel receives values. In this case, Go will error with a deadlock.

There are of course other techniques to don’t hardcode the number of sending or receiving signals. Take a look at closing a channel if you are interested in it. But I want to keep this post simple. So I don’t get more into detail yet.

It was pretty easy to start with goroutines. In the past, I always heard that “the Go concurrency api” is nice and easy to use, compared to other languages. Today I can say: “Yep. They are right”. It took me only a few hours to understand and implement goroutines in my application.

Learning sources

A Tour of Go, the first four concurrency chapters.

--

--