Concurrency in Go: Goroutines

Anil Vedala
4 min readJun 27, 2020

--

Photo by Wendy van Zyl from Pexels

Go is a programming language popular for its simplicity and the built-in features supported for concurrent programming. Mastering the core concepts of concurrency in Go is essential for building cost-efficient distributed applications. Before diving into the specifics about the libraries and the language specifications, let us revisit the concept of concurrency in computation.

Concurrency is the ability to deal with more than one task in the execution context. Parallelism is built upon Concurrency. It is the ability to run multiple tasks at the same moment.

For example, a single-core processor in the current day is a concurrent system as it will have a lot of tasks in its execution context at any time. It switches from one task to another when the first task is blocked for I/O or waiting for the user input etc., thereby achieving better throughput than a processor that executes tasks in a sequential manner. A multi-core modern day processor is an example of a Parallel system as at any moment, it can actually run multiple tasks distributing one for each of its cores.

In Operating-systems’ domain, a single unit of execution( referred as a task, above) is called a thread. Generally speaking, Operating systems have hundreds, if not thousands of threads running at any point in time. Programming languages have their run-times mapping these application threads with the Operating-system level threads.

Every programming language supports concurrency with the following aspects —

  1. ability to create application-level threads
  2. features that enable the co-ordination between the threads: wait and notify
  3. memory sharing/data transfer among the threads.

In Go, the above aspects are met with the following constructs/libraries —

  1. Goroutines,
  2. Sync package — Wait-groups and Mutex locks,
  3. Channels.

First, let us have a look at what Goroutines are how to create one.

Goroutine

Goroutines are the primary constructs for concurrent programs in Go. These are similar to threads in other languages like Java but light-weighted. These are managed by Go’s run-time. It maps a smaller pool of OS-level threads to a bigger number of Goroutines and the execution is multiplexed among them. That means, more than one number of Goroutines are actually mapped with a single OS-level thread, thereby reducing the resource usage.

This resource optimization is much needed so that the Operating-system’s memory resources are not entirely sucked up. Once, I’ve encountered a web service written in Java that had crashed in production with out of memory. When I was debugging the issue, I’ve realized that because of the parameters in the specific request, the service ended up creating thousands of application threads that have a 1:1 mapping to Operating system’s threads and so the computing machine went out of memory. Hence, having this many-to-one mapping between Goroutines and OS threads is a plus.

Goroutines are easy to create. It is as simple as the below snippet —

go f(x, y)

The above line creates an independent line of execution from the main thread and runs the function defined after the go keyword. This function is no different from the other ones we use in Go.

To understand more about the parallel execution of the main thread and Goroutines, let us consider the following case — We want to print numbers from 1 to 100 in any order with the main thread and a Goroutine. Specifically, let the Goroutine print numbers from 1 to 50 and the main thread from 51 to 100.

For this, let us create a function that takes the executor name, starting number, and ending number like this —

and let both the threads use the function. Let us add the main function and call the Goroutine from it.

That’s it, let us run this with go run main.go

Below is a section of the total output —

See how the print-log is interleaved above, with a mix of printed numbers from both the main thread and the Goroutine. This is because, they are running simultaneously and independently in a separate line of executions, and pushing the print statements to the console.

In real-world use cases, although Goroutines are primary, they are not just sufficient alone. We often come across the standard Concurrency use cases such as the reader-writer problem etc., where it is necessary for data transfer between Goroutines or sharing specific pieces of memory. That is where the features like Mutex locks, Wait-Groups, and Channels come into the picture. These topics are covered in this article.

Wanna play around with Goroutines? try here — https://play.golang.org/

--

--