In 2006 Intel released the first dual-core CPU. Shortly after, a language (Go) that could natively provide features to benefit from multi-cores came into existence.

Introduction

Concurrency is executing a set of instructions in any indefinite order and still being able to produce the same output of a program as if functions are executed sequentially.

Simultaneous execution of instructions is seen everywhere in modern-day programs. For example, we can see it in a web server serving multiple web requests or compiling code in our IDE while still being able to edit it. This term is often confused with Parallelism.

Let’s say you have a sandwich and a milkshake you need to finish in 3 minutes. You can do two things:

Choice 1:

1. Either take a bite of sandwich, chew.

2. Take a sip of shake, drink.

3. Repeat the process until you finish both under 3 minutes.

Choice 2:

1. You can take put the whole sandwich in your mouth, bottoms up the milkshake.

2. Try swallowing both of them together.

3. Wait till you finish both under 3 minutes.

My eating superpowers

Choice 1 is Concurrency, Choice 2 is Parallelism. Concurrency is “in progress at the same time” and parallelism is “executing simultaneously”. You can choose any depending upon the time, place, and vicinity of friends to take full benefit of each. 😛

Two functions may be able to run concurrently but they may not be Parallel. Parallel execution means two or more independent instructions are being evaluated at same time, this is only possible if there are multiple cores available to run those instructions. Concurrency is about dealing with lot of things at once. Parallelism means doing those things at one time.

Concurrency is not parallelism, but it does enable parallelism. If you have one processor, your code cannot be parallel, but it can be concurrent. If multiple processors are available, Go can run your concurrent code in parallel.

Visual 1, Parallelism and Concurrency

Parallelism and Concurrency

To understand Concurrency, we will first see a program that runs its set of instructions in a sequence. Let’s consider a simple program that checks whether a site is up or not by making a call to its homepage.

A Slice of strings which are looped while making http GET request to each link

On line:9, we declared a slice of strings (say array) On line:16, we declared a range to loop over the slice of strings and call checkLink function for each element (that is the link) in the slice. On line:22, The checkLink function makes a GET request to the link, wait for the response to come back and then logs the success or error response.

Output:

As observed in output, there is a distinct delay after each link is called. For each request, we wait for the request to come back with the response. In between each fetch there is no other path of execution that can be executed. This makes it a sequential program.

Visual 2, Program Flow

Flow of the program

The path of execution of a code is called a routine. In Go they are called goroutines. Each Go program comes with a minimum of one goroutine which is the main function from which program execution begins.

The time slice of waiting has blocked our routine to proceed further. It is a waste of the multi-core resource we have. Let’s use goroutine to solve this.

Goroutine

A goroutine in Go gives our program a new path of execution. These can be thought of similar to Threads in languages like C# and Java (they are actually different but let’s draw some pictures here). A goroutine has a lighter footprint on the system. A thread consumes 1MB of memory due to the larger stack size, a goroutine starts with only a fraction of that (2KB) because its stack is resizable.

Think of goroutines as application-level threads. Just as OS Threads are switched on and off the hardware cores by the Operating System, goroutines are also context switched on each assigned OS Thread.

You can get the number of CPU cores available for your program using:

// NumCPU returns the number of logical

// CPUs usable by the current process.

fmt.Println(runtime.NumCPU())

Goroutines have no unique identifier, name, or data structure that they return, they are only anonymous workers. If interested seasoned gophers can read about it here.

Using Goroutine

A new routine can be started by using keyword go followed by a function call. The arguments are evaluated in the main goroutine, but the function is executed in a new goroutine.

for _, link := range links {

go checkLink(link)

}

Now each checkLink function executes in a new goroutine (path of execution) and the main goroutine is not blocked

Output:

As you can see there is nothing is logged on the console. The main goroutine fired new goroutines for each link, but it did not wait for them to finish.

Since there is nothing to execute after the loop which fired the goroutines, the program exited.

Visual 3, Multiple Goroutines Running Simultaneously

Multiple goroutines running simultaneously

It will be evident in the following code:

Output:

Spawning more goroutines may not necessarily result in higher performance of your program as they may not execute at same time. We will learn about it at end of this article.

We need a way to communicate among different goroutines (in this instance, to inform the main goroutine about their execution) and synchronize their tasks. To make a goroutine publish a message to another goroutine we need a PIPE. In one end, the goroutine can publish the message, and on the other end, another goroutine which needs can listen using what we know as Channels.

Channels

Channels are these pipes via which different routines can send and receive values of a certain type between different goroutines.

Visual 4, Multiple Goroutines Communicating

Multiple goroutines communicating with the help of channels

Quoting from official golang tour:

Channels are a typed conduit through which you can send and receive values with the channel operator, <- .

To declare a channel we use:

c := make(chan int)



go func() {

c <- 42 // Send data to channel

}()



fmt.Println(<-c) // Receive data from channel

Play it here

In the channel c , we can send and receive integer values. Note: The data flows in the direction of arrow Once a routine sends something to the channel, the code execution routine is blocked (by default) until the value sent to the channel is being received on the other end at the same time. By “same time”, we mean the routine in which the channel is expecting to receive should be ready to receive at the same time when the data is sent on the channel. See here, we cannot send and receive in the same goroutine because to send to a channel the receiving end will become active at a different time. That is when code execution reaches that statement of receiving channel ( <-c ). If there is no receiver, the routine will block forever and eventually will lead to fatal error in Go. You can try it here

Let’s apply goroutines and channels to our program.

Output:

Note: The output will vary as per internet speed and number of GOMAXPROCS that is number of goroutines that can execute at once.

We can use a familiar syntax of traditional for loop to receive from the channel or use a range loop from Golang.

for index := 0; index < len(links); index++ {

fmt.Println(<-c)

}

The traditional loop is blocking because it cannot complete the iteration until it receives something from any of the channels.

The range loop from Go on the other hand, receives values from the channel repeatedly until it is closed.

for msg := range c {

fmt.Println(msg)

}

Note: To come out of range loop we should always close from the sending channel otherwise it would keep waiting forever.

Here’s the final code:

Spawning more goroutines than the actual number of OS threads may slow down the program. For some situations writing concurrent program becomes obviously beneficial. Remember your sandwich and milkshake? It may be a good idea to follow Case 2 when you have bet on your eating superpowers but may not be when you are on your first date with your crush. 😛

For a deeper understanding of Go concurrency, you can watch the following video:

To Summarize

By understanding the type of work your program is going to handle, we can make a wise decision. For CPU-heavy work (like an awfully large for loop or calculating the fibonacci of a rather big number) the threads are never idle. In this case, parallel running goroutines on multiple OS/hardware threads are going to produce performance boost.

If you have more goroutines than available OS/hardware threads, there is going to be a latency cost by switching goroutines on and off on each thread. More routines would have to wait.

For IO heavy work (like accessing the file system or making network calls) the threads have to wait for the OS to complete the task. In this case, the OS/hardware will become idle and other goroutines will get a chance of execution. Hence concurrency without parallel execution will benefit here.

You did great 😇

Thank you for making it this far.

Important things like the type of communication mechanism between channels, some design patterns around it, and a real-world example are left for a separate discussion. You can shoot your doubts or drop a hello on my twitter. Please consider some feedback or sharing the article for those who may benefit from it.