Anatomy of goroutines in Go -Concurrency in Go

goroutine is lightweight execution thread running in the background. goroutines are key ingredients to achieve concurrency in Go.

What are Goroutines?

Goroutines are functions or methods that run concurrently with other functions or methods. Goroutines can be thought of as light weight threads. The cost of creating a Goroutine is tiny when compared to a thread. Hence its common for Go applications to have thousands of Goroutines running concurrently.

Advantages of Goroutines over threads

  • Goroutines are extremely cheap when compared to threads. They are only a few kb in stack size and the stack can grow and shrink according to needs of the application whereas in the case of threads the stack size has to be specified and is fixed.
  • The Goroutines are multiplexed to fewer number of OS threads. There might be only one thread in a program with thousands of Goroutines. If any Goroutine in that thread blocks say waiting for user input, then another OS thread is created and the remaining Goroutines are moved to the new OS thread. All these are taken care by the runtime and we as programmers are abstracted from these intricate details and are given a clean API to work with concurrency.
  • Goroutines communicate using channels. Channels by design prevent race conditions from happening when accessing shared memory using Goroutines. Channels can be thought of as a pipe using which Goroutines communicate. We will discuss channels in detail in the next tutorial.

How to start a Goroutine?

Prefix the function or method call with the keyword go and you will have a new Goroutine running concurrently.
Lets create a Goroutine :)

Well, as per goroutine syntax, we prefixed function call with go keyword and program executed well. It yielded the following result.
main execution started main execution stopped

It is a bit strange that Hello World did not get printed. So what happened?

goroutines always run in the background. When a goroutine is executed, here, Go does not block the program execution, unlike normal function call as we have seen in the previous example. Instead, the control is returned immediately to the next line of code and any returned value from goroutine is ignored. But even then, why we can’t see the function output?

By default, every Go standalone application creates one goroutine. This is known as the main goroutine that the main function operates on. In the above case, the main goroutine spawns another goroutine of printHellofunction, let’s call it printHello goroutine. Hence when we execute the above program, there are two goroutines running concurrently. As we saw in the earlier program, goroutines are scheduled cooperatively.

Hence when the main goroutine starts executing, go scheduler dot not pass control to the printHello goroutine until the main goroutine does not execute completely. Unfortunately, when the main goroutine is done with execution, the program terminates immediately and scheduler did not get time to schedule printHello goroutine.

But as we know from other lessons, using blocking condition, we can pass control to other goroutines manually AKA telling the scheduler to schedule other available goroutines. Let’s use time.Sleep() call to do it.

We have modified program in such a way that before main goroutine pass control to the last line of code, we pass control to printHello goroutine using time.Sleep(10 * time.Millisecond) call. In this case, the main goroutine sleeps for 10 milli-seconds and won’t be scheduled again for another 10 milliseconds.

Once printHello goroutine executes, it prints ‘Hello World!’ to the console and terminates, then the main goroutine is scheduled again (after 10 milliseconds) to execute the last line of code where stack pointer is. Hence the above program yields the following result.
main execution started Hello World! main execution stopped

If we add a sleep call inside the function which will tell goroutine to schedule another available goroutine, in this case, the main goroutine. But from the last lesson, we learned that only non-sleeping goroutines are considered for scheduling, main won’t be scheduled again for 10 milli-seconds while it’s sleeping.

Hence the main goroutine will print ‘main execution started’, spawning printHello goroutine but still actively running, then sleeping for 10 milli-seconds and passing control to printHello goroutine.

printHello goroutine then will sleep for 1 milli-second telling the scheduler to schedule another goroutine but since there isn’t any available, waking up after 1 milli-second and printing ‘Hello World!’ and then dying. Then the main goroutine will wake up after a few milliseconds, printing ‘main execution stopped’ and exiting the program.

Above program will still print the same result
main execution started Hello World! main execution stopped

. . .

Starting multiple Goroutines

Lets write one more program that starts multiple Goroutines to better understand Goroutines.

The above program starts two Goroutines in line nos. 21 and 22. These two Goroutines now run concurrently.

The numbers Goroutine sleeps initially for 250 milliseconds and then prints 1, then sleeps again and prints 2 and the same cycle happens till it prints 5.

Similarly the alphabets Goroutine prints alphabets from a to e and has 400 milliseconds sleep time. The main Goroutine initiates the numbers and alphabets Goroutines, sleeps for 3000 milliseconds and then terminates.

This program outputs
1 a 2 3 b 4 c 5 d e main terminated

The following image depicts how this program works. Please open the image in a new tab for better visibility :)

The first portion of the image in blue color represents the numbers Goroutine, the second portion in maroon color represents the alphabets Goroutine, the third portion in green represents the main Goroutine and the final portion in black merges all the above three and shows us how the program works.

The strings like 0 ms, 250 ms at the top of each box represent the time in milliseconds and the output is represented in the bottom of each box as 1, 2, 3 and so on. The blue box tells us that 1 is printed after 250 ms, 2 is printed after 500 ms and so on.

The bottom of last black box has values 1 a 2 3 b 4 c 5 d e main terminated which is the output of the program as well. The image is self explanatory and you will be able to understand how the program works.
. . .

Anonymous goroutines

If an anonymous function can exist then anonymous goroutine can also exit. Please read Immediately invoked function from functions lesson to understand this section. Let’s modify our earlier example of printHello goroutine.

The result is quite obvious as we defined the function and executed as goroutine in the same statement.
All goroutines are anonymous as we learned from concurrency lesson as goroutine does not have an identity. But we are calling that in the sense that function from which it was created was anonymous.

That's it for Goroutines. Have a good day.

On a mission to build Next-Gen Community Platform for Developers