Implementing Concurrency with Go

Kerala Blockchain Academy
5 min readJul 26, 2023

--

By Mobin Mohanan, Research Engineer at Kerala Blockchain Academy.

Have you heard of concurrency in computer programming?

Well, concurrency is the ability of a computer program to do multiple activities at the same time. Many of the common programming languages provide concurrency with the help of threads. Go language uses a different concept to implement concurrency with ease. I will show you how Golang implements concurrency using goroutines in this article.

Let’s start with a Go program. We will use an online editor to free you from the installation overheads.

Go to go.dev/play. This playground allows us to run our Go programs without installing Go.

If you’re a beginner, check out this cheat sheet for quick reference: https://gist.github.com/DEMYSTIF/6e18f05e5338e1a5a4426537f8cba5bd

Now, let’s write a simple Go program to print messages from Earth and Mars on the console.

package main

import (
"fmt"
)

func main() {
fmt.Println("Hello from Earth")

fmt.Println("Hello from Mars")

fmt.Println("Hello again from Earth")
}

By declaring package main at the top, we can create a standalone executable with a main function. Also, we import the "fmt" package from Go to print out messages to the console.

Paste the code in the editor and click ‘Run’ to execute it.

You get an output like this:

Hello from Earth
Hello from Mars
Hello again from Earth
Program exited.

That’s good so far.

Now I will edit the second message by changing it to an anonymous function and executing it. Since Mars is far away from Earth, there should be a delay in the message. I will use Go’s inbuilt "time" package to create a sleep function inside the anonymous function.

Our updated code is

package main

import (
"fmt"
"time"
)

func main() {
fmt.Println("Hello from Earth")

func() {
time.Sleep(5 * time.Second)
fmt.Println("Hello from Mars")
}()

fmt.Println("Hello again from Earth")
}

I am using a delay of 5 seconds. Let’s rerun it.

We got the same output, but you may notice the delay between the first and second messages. So it’s working. But there’s an issue here. The third message is also being delayed. That’s not right. Since it is also from Earth, it should be printed with the first message.

Have you got any solution? Yeah, I know what you’re thinking. We can add the line before the anonymous function. Then it will work. But we are here to learn concurrency, so let’s jump to goroutines.

Goroutines: What are they?

Goroutines are functions that can run concurrently with other functions. The language runtime manages them. The Go process scheduler operates goroutines by tying them to the operating system’s threads. Compared to other languages like Python and Java, which uses threads, goroutines are cheaper and lightweight.

Now it’s time to create our goroutine. We can add the anonymous function to a separate thread using the goroutines.

How do we do that? It’s simple, add go before the function like this:

package main

import (
"fmt"
"time"
)

func main() {
fmt.Println("Hello from Earth")

go func() {
time.Sleep(5 * time.Second)
fmt.Println("Hello from Mars")
}()

fmt.Println("Hello again from Earth")
}

Now the message from Mars should be running on a separate thread that will not block the execution of the main thread. Let’s run it.
The output will be:

Hello from Earth
Hello again from Earth
Program exited.

Wait!! Where’s the message from Mars? It’s not here.

Well, don’t worry. The issue is that the Go program doesn’t wait for the separate threads to finish; when the main thread is done, it will exit the program.

How about we tell the go runtime to wait till all the threads are done? We have WaitGroups available in Go’s inbuilt "sync" package for that.

A WaitGroup waits for a set of goroutines to finish. The main goroutine calls Add() to set the number of goroutines to wait for. Then each goroutine runs and calls Done() when finished. At the same time, Wait() can block the rest of the execution until all goroutines have finished. To use WaitGroup, we should include the following steps in our program.

  • Create a new instance of sync.WaitGroupLet’s call it wg.
  • From the main goroutine, call Add function to set the number of goroutines to wait for. In our case, it will be wg.Add(1).
  • Indicate the end of the goroutine using the Done function. We should include wg.Done() after the message from Mars.
  • In any case, you need to block until the goroutine has finished; you may use wg.Wait().

The updated code is given below:

package main

import (
"fmt"
"sync"
"time"
)

func main() {
var wg sync.WaitGroup
fmt.Println("Hello from Earth")

wg.Add(1)
go func() {
time.Sleep(5 * time.Second)
fmt.Println("Hello from Mars")
wg.Done()
}()

fmt.Println("Hello again from Earth")
wg.Wait()
}

Here I have declared a WaitGroup variable wg inside the main function. After the message from Earth, I’ve added our anonymous function to that wait group using wg.Add(). Since we only want one separate thread, I have given 1 as an actual parameter. To flag the end of our process, I have specified wg.Done() towards the end of the anonymous function and wg.Wait() to the last line of our main function.

Now let’s execute it.

Hello from Earth
Hello again from Earth
Hello from Mars
Program exited.

Hooray!! We’ve got it. Our work has finally paid off!!
Way to Go (pun intended)!!

Here’s an overall look at our program:

  1. The main function starts by importing the required packages: "fmt" for basic I/O, "sync" for synchronization primitives, and "time" for handling time-related operations.
  2. A WaitGroup variable wg is declared to coordinate the concurrent goroutines. It will be used to wait until all goroutines finish their execution.
  3. The program starts by printing “Hello from Earth” to the console.
  4. Next, the main function calls wg.Add(1) to add one goroutine to the WaitGroup. This indicates that there is one goroutine that needs to be waited upon.
  5. A new goroutine is started using an anonymous function defined with the go keyword. This goroutine will print “Hello from Mars” after waiting 5 seconds.
  6. While the Mars goroutine is sleeping, the main function continues executing, and “Hello again from Earth” is printed on the console.
  7. At this point, the main function will block (stop and wait) the execution. This means the main function will not proceed until the WaitGroup’s counter is back to zero, which happens when all goroutines added with wg.Add() have called wg.Done().
  8. Inside the Mars goroutine, it sleeps for 5 seconds using time.Sleep(5 * time.Second) to simulate a delay.
  9. After sleep, the Mars goroutine prints “Hello from Mars” to the console.
  10. Finally, it calls wg.Done() to signal the WaitGroup that it has finished its task.
  11. The main function can proceed beyond wg.Wait() and exit since WaitGroup’s counter has reached zero, and all goroutines have finished.

Did you like this simple demo of goroutines? This is not all. We can also communicate between goroutines, but that’s for another time.

--

--

Kerala Blockchain Academy

One-stop solution for quality blockchain education and research. Offers best in class blockchain certification programs in multiple blockchain domains.