The beauty of concurrent programming in Go

Yasser Sinjab
3 min readOct 15, 2020

“Time is nature’s way of keeping everything from happening at once.”

Any code you write will run sequentially on single machine. But let’s think more: can we do more than one task at a single point of time? I mean (concurrently). The answer is: yes.

Firstly let’s discuss why we need concurrency. There are many reasons but the main two are:

  • Performance: fast processes won’t be delayed or wait for slower components to do their jobs. The will focus on their tasks.
  • Simplicity: Big tasks will be broken into small tasks where they can work in more efficient way, tested individually and fixed separately.

So why some processes usually wait for each other? and why some of them are slow and others are fast?

Usually for one of two reasons: either the process is I/O bound: it is waiting for network or disk access, or it is CPU bound: where it is a number crunching process and doing a lot of calculation.

Go models concurrency as primitives in the language itself. But before we dive into concurrency patterns and those primitives let me answer: why concurrency is so hard?

Go concurrency primitives

Goroutines

Every go program has at least one goroutine: the main goroutine (it is automatically created when you run the process). Andother goroutines that can run concurrently to another part of the code.

Note that goroutines are not OS threads. Read why goroutines instead of threads?

The sync package

This includes :

  • waitgroup: To wait for multiple goroutines to finish, we can use a wait group. See example
  • Mutex: to safely access data across multiple goroutines. See example
  • Cond: Cond implements a condition variable, a rendezvous point or goroutines waiting for or announcing the occurrence of an event (any arbitrary signal between two or more goroutines that carries no information other than the fact that it has occurred.)
  • Once: See example
  • Pool: See example

Channels

Channels are best used to communicate information between goroutines. It is a stream of information, like a river.

Select statement

select is the glue that binds multiple channels together. select statements can help safely bring channels together with concepts like cancellations, timeouts, waiting, and default values. Please read select example to know more about it.

Concurrency patterns in Go

Some of the popular patterns are:

The for-select Loop

This pattern is nothing more than something like this:

for { // Either loop infinitely or range over something
select {
// Do some work with channels
}
}

Here is an example:

Fan-In & Fan-Out

Usually this pattern is used when you have a pipeline that handle stream of data, but at one point of time one of the stages in the pipeline start becoming computationally expensive and taking long time to process. This impact the whole pipeline performance. What if we can run multiple goroutines that pull data from stream and attempt to parallelize the work on them?

This is what this pattern solve. Fan-out is the process of starting multiple goroutines to handle stream of data from the pipeline, and fan-in is the process of combining multiple results into one channel.

In the following example I wrote fibonacci number calculator which is expensive for some numbers.

Hope this will help reader on how to write some beautiful code that can handle concurrency problems in an easy way. In next posts I will try to explore more patterns and share it with the community.

--

--

Yasser Sinjab

Software Engineer. Data nerd. Machine learning enthusiast.