So, what the hell are goroutines?

Written by alkeshsrivastava11 | Published 2017/12/07
Tech Story Tags: golang | goroutines | programming | technology | software-development

TLDRvia the TL;DR App

The image is no over exaggeration

Native support for concurrency is by far one of the most popular features of Go which allows developers to create concurrent applications easily. In order to take advantage of this native concurrency, goroutines are needed.

Goroutines can be considered the backbone of Go and are important in designing inherently concurrent applications.

So let’s understand what goroutines are.

What are Goroutines

Goroutines can be compared to light weight threads. They are just functions or methods that run concurrently with other functions or methods.

They allow us to create and run multiple functions concurrently in the same address space and have no significant setup cost.

Goroutines vs Threads

Go uses goroutines while other languages such as Java uses threads. Goroutines are cheap(around 2 Kb) where as threads are often memory intensive(beginning at 1 Mb) as they require more stack space. This allows developers to create a large number of goroutines which could not have been possible with threads.

So you might ask, why are goroutines so light weight?

Well, the reason why goroutines are so light weight is because of the fact that Go doesn’t use the conventional native threads but rather the green threads. Native threads or rather the OS threads have individual function call stack and their setup and tear down cost is significant.

Let’s see how Go does that.

So at runtime, the scheduler maps the goroutines onto the threads but unlike the 1:1 mapping in Java threads, a bunch of goroutines are mapped onto a single or a group of threads. Also, goroutines have flexible stack size that expands and shrinks as and when required. This significantly reduces the memory footprint and improves the memory reallocation time for goroutines.

But you don’t need to worry about all these nifty details at all.

All these tasks are taken care of by Go at runtime and is abstracted from the user.

So the user can do what he does best, write clean and efficient code and not to be worried about stack segmentation and memory reallocation.

The gopher-books analogy from Rob Pike’s talk

How to start a Goroutine

A goroutine is fairly simple to start and can be done simply by using the keyword go in front of the function or method and you get a goroutine ready to work concurrently.

package mainimport (      "fmt")func hello() {      fmt.Println("Hi, I'm a goroutine!")}func main() {      go hello()    fmt.Println("Inside main function")}

The above code will create a goroutine to run concurrently along with the func main() which executes its own goroutine known as the main goroutine.

The creation of goroutines is very simple and you don’t have to worry about the finer details in the background as they all are handled at runtime by Go.

But wait..

On execution of the code, you’ll receive a little surprise!

Inside main function

The code only prints the message from the func main()and not the message from func hello(). This can be explained as follows —

  1. When a goroutine is started, the goroutine call gets returned immediately. What it means is that the control does not wait for the goroutine to finish executing in order to proceed with the next function or goroutine.
  2. Also the main goroutine should be running in order for any other goroutine to run. Its termination would lead to the termination of any other goroutines and the program will end.

That’s precisely what happened with our code above. After a call to go hello the control printed the message in the main function and the program terminated after that. The func hello() didn’t get a chance to be executed because the main goroutine got terminated and the program ended.

The fix to the problem

To be precise, it’s more of a hack than a fix because we are bounding the execution of the program to real world time constraints but that would be suffice for this example. Note that this is a bad practice and should be avoided as in reality we always use channels for any such synchronisation.

package mainimport (      "fmt"    "time")func hello() {      fmt.Println("Hi, I'm a goroutine!")}func main() {      go hello()    time.Sleep(1 * time.Second)    fmt.Println("Inside main function")}

The time.sleep(1 * time.Second) line puts the current goroutine to sleep and gives the hello goroutine enough time to finish.

Now when we run the program we get both the messages with a 1 second gap.

Hi, I'm a goroutine!Inside main function

Closing thoughts

By default Go uses the number of CPU threads equal to the number of CPU cores present. You can get the number of threads by the following statement runtime.GOMAXPROCS(-1)

The number of threads can be changed by changing the value from 1 to any number but care must be taken while choosing them. Though more threads increase performance but too many can slow the process down so the ideal amount should only be decided after a running a performance sweep.

Go’s native concurrency model has allowed it to rise in popularity for creating truly concurrent systems and goroutines are an important part of its functioning. They can be considered the heart and soul of the language and if used correctly, they can provide massive performance boost.

Concurrency is not parallelism

I’ve used concurrency a lot of times in the post but if you’re still tempted to use the words concurrency and parallelism interchangeably then its high time you watched Rob Pike’s take on the topic.


Published by HackerNoon on 2017/12/07