How to implement concurrency in Go

Subscribe to my newsletter and never miss my upcoming articles

Go was designed to run on modern computers with multiple cores. Go has first-hand support for concurrency through goroutines and channels. Writing a concurrent program in Go is as simple as adding a single keyword. Let's dive straight into the implementation.

What is Concurrency?

Let me give a simple example: I am reading a book and I got a text from my friend. Now I stop reading to check my phone to see the text once I am done with it I go back to reading my book. Here what I am really doing is dealing with many tasks (two in this case). People often confuse concurrency with parallelism because they seem similar. Parallelism is performing tasks at the same time(Writing while listening to your favorite music). You can say parallelism is part of concurrency but vice versa is not true.

Concurrency is about performing many tasks independently. They may or may be getting performed at the same time

Want to know more about concurrency go check out this talk by Rob Pike

Simple Example

Here is an example of a simple program in Go that performs a simple job of printing the tasks.

package main

import (
    "fmt"
)

func performTask(task int) {
    fmt.Println("Performing", task)
}

func main() {
    performTask(1)
    performTask(2)
    performTask(3)
}

Output

Performing 1
Performing 2
Performing 3

The above programs work flawlessly without any issue. Now let's make it concurrent. To make something run concurrently in go we use the in the built keyword go somethingYouWantToRun(). Basically the go keyword runs a function as a goroutine.

What is a Gouroutine

A goroutine is a lightweight thread managed by the Go runtime. Basically goroutine is an abstraction on top of thread where m goroutines run on n os thread. So practically you might be running 1000's of goroutine on just 3-4 os thread without even noticing a small difference in performance.

Example with goroutine

We will now add the go keyword before performTask(2) to make it run in the background.

package main

import (
    "fmt"
)

func performTask(task int) {
    fmt.Println("Performing", task)
}

func main() {
    performTask(1)
    go performTask(2)
    performTask(3)
}

output

Performing 1
Performing 3

As you can see the output of the above program and realize it doesn't contain the Performing 2 text. Let me explain to you what exactly is happening here, the code with the go keyword is getting run in the background, so by the time it can print output the main thread returns and doesn't print the Performing 2.

So how can we fix this? Let me show you.

package main

import (
    "fmt"
    "sync"
)

func performTask(task int, wg *sync.WaitGroup) {
    fmt.Println("Performing", task)
    wg.Done()
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i <= 2; i++ {
        wg.Add(1)
        go performTask(i, &wg)
    }
    wg.Wait()
}

output

Performing 2
Performing 0
Performing 1

Now you can see we are getting all the three tasks printed. Let me explain to you what we did. We are using wait group to achieve the above concurrency.

  • var wg sync.WaitGroup: A WaitGroup waits for a collection of goroutines to finish.
  • wg.Add(1): Add increments the WaitGroup counter by one.
  • wg.Done(): Done decrements the WaitGroup counter by one.
  • wg.Wait(): Wait blocks until the WaitGroup counter is zero.

So what we are doing above is incrementing the counter by one every time we create a goroutine so that we can wait for all the goroutine to finish at the end by using wg.Wait(). Every time a goroutine is done with its job it runs wg.Done() which basically decrement the counter for the number of goroutines to wait.

Conclusion

You can implement concurrency in Go in various ways. I have shown you one of the ways. You can look into channels and other concurrency patterns in Go.

If you liked this blog give me a shout-out in the comment and do share it on twitter. Also do let me know what would like me to write next.

Comments (2)

Asad Awadia's photo

Instead of doing wait groups yourself - it might be less error prone to use error groups instead godoc.org/golang.org/x/sync/errgroup

It also allows returning the first error from the child goroutines

Umesh Yadav's photo

Hey, yes it’s one of the way to implement concurrency but here we don’t want to propagate the error and fail when a goroutine fails. The above one is more of a beginner guide how one can implement concurrency with least overhead.