A Beginner’s Guide to Concurrency in Go

Zubeen
4 min readNov 21, 2022

--

Go has been around for over a decade now, and with it’s simplistic learning curve and with the addition of new features coming along with each release, the increase in adoption rates and interest in learning the language, comes as no surprise.

Source: HackerRank Developer Skills Report 2020

Now that we know, how popular Go is, let’s bring back our focus to the topic of this blog, which is, *drumroll* Concurrency. Before we deep dive into what Concurrency is and how we implement it with Go, let’s clear out a few things before.

If you’re coming from a programming background you must have come across the following terms: Concurrency, Parallelism, and Multithreading in some form of a synonymous context.

As similar as these might sound, it becomes very important to know the subtle differences between each one of them. In simple terms, we can define them as:

Concurrency: the ability to handle several tasks at once

Parallelism: handling several threads at once

Multithreading: the process to execute multiple tasks simultaneously wherein each task may/may not, have several sub-tasks within them

In the context of the above definitions, when we speak of Concurrency in Go, we distinct programming constructs:

  • GoRoutines
  • Channels

Let’s go through each one of them in detail:

GoRoutines

Any concurrent execution in Go is handled by a special function that acts like a light-weighted thread and is known as a goroutine. This implies that any function that is essentially a goroutine, starts executing individually irrespective of the function that invoked it.

Like any other programming language where the execution starts from the main method, Go being no different does the same where the execution begins from the main function, which essentially is a goroutine. This implies that any and every program that you run in Go has at least one goroutine, which is the main function.

Goroutines can start executing parallelly once they get invoked

Let’s now investigate a very simple program with a goroutine and see how it works:

Try it yourself at the Go Playground

Let’s assume we had a Treasure Chest consisting of 3 Diamonds and 3 Rubies. Assuming that the treasure got stolen, we sent out a team of two workers to find out the missing gems.

Now, the first worker works synchronously, which means that he would work in sequential order, collect all the diamonds, and return with an update once he has found all of them. The second worker, however, works in an asynchronous fashion, wherein he’ll update the status as soon as he finds any one of the missing rubies.

The differentiating factor in these two approaches lies in the fact as to how the function treasureChest() is being invoked by each one of them. The first approach in line 15, is the common way of invoking a function and works in the sequence as it is supposed to. Appending go to the start of this function call on line 17 converts it into a goroutine, which will then start executing independently out of whatever operations are taking place on the main thread. So as soon as worker two finds something, you’ll get a print statement indicating the same.

To see how they work concurrently you’ll have to use the sleep method. Running your code without sleep would make you think that the goroutine isn’t invoked at all, whereas this isn’t the case as the main thread executes so fast that it doesn't give enough time to the other thread to get it’s statements printed out, and exits before that.

Channels

Channels are a way that allows the to and fro communication between different goroutines.

Let’s have a look at how channels enable communication, using our previous example where two workers were assigned the responsibility of finding the lost gems.

Try it yourself using the Go Playground

In the above code, the execution starts with the main function. We then create a new channel using the make keyword and specify the type of value that the channel would use while communicating, which in this case is a string. We then invoke the worker1 function as a goroutine which will start printing messages. The message communicated by worker2 on the channel (line 7) is stored in a variable named status and is then printed alongside the message that worker1 has to display.

There are several in-built functions such as cap(), len() that allow you to get more information about your channels such as the length and the capacity. You can make use of these to get clarity in how you want your goroutines to communicate and make use of this feature more efficiently.

The next step in learning more about concurrency and goroutines is to know what concurrency patterns are. To know more about them, you can read the official blog linked here.

That’s all gophers, stay tuned for my upcoming blogs. Ciao!

--

--

Zubeen

Unleashing the Tech Enthusiast within: SWE diving into the realms of Golang, Flutter, Cryptography, and NLP! 🚀🔒📊📝