Concurrency and Goroutines in Golang
Introduction
What is concurrency?
Concurrency can be described as the composition of independently executing processes or tasks. In other words, concurrency is the ability to run multiple tasks at the same time. For instance, if you are watching a movie and you are also texting a friend, you are doing two things at the same time but not simultaneously. This is concurrency.
Concurrency is different from parallelism. Parallelism is the ability to run multiple tasks simultaneously. For instance, a person can dance and sing at the same time. This is parallelism because the person is doing two things simultaneously.
Even though Golang is a concurrent programming language, it doesn’t mean that you can’t write parallel programs in Golang.
Concurrency in Golang
Golang is termed as a concurrent programming language since it has a built-in concurrency model achieved through goroutines and channels.
Goroutines
Goroutines are lightweight threads, they are multiplexed onto multiple kernel threads and are managed by the Go runtime which is responsible for scheduling goroutines onto kernel threads.
They are termed lightweight since the overhead of creating a goroutine is very small and cheap, unlike spinning up another thread in the kernel space.
You can learn more about the Golang runtime scheduler here.
How to create a goroutine
Golang provides an easy API to create a goroutine. The keyword go
is used to create a goroutine and is placed in front of the function call.
Note: The main function is also a goroutine referred to as the main goroutine.
By running this program you will see that it only prints Hello from main
and not the message from the goroutine. This is because the main goroutine exits before the other goroutine can print the message since it is not blocking. To fix this, we can use a timer to block the main goroutine for a certain amount of time.
The timer is not the best way to block the main goroutine from exiting. We can use the WaitGroup
function. This introduces the topic of synchronization.
Synchronization
WaitGroup
WaitGroup
is a synchronization primitive that allows us to wait for a collection of goroutines to finish. It is provided by the sync
package.
Let’s solve the previous problem using WaitGroup
.
The WaitGroup
is just a counter that keeps track of the number of goroutines that are waiting to finish. The Add
function increments the counter by the number of goroutines that are waiting to finish, and the Done
function decrements the counter by 1, thus the need to call this function inside the goroutine. The Wait
function blocks the main goroutine until the counter is 0.
To demonstrate a better use case of WaitGroup
and synchronization problems in general, let’s write a program that calculates the Fibonacci sequence.
In this example, we are creating two goroutines that calculate the Fibonacci sequence. The program will wait for both goroutines to finish before exiting.
The order of execution of a goroutine is non-deterministic.
Channels
Channels are a typed conduit through which you can send and receive values with the channel operator, <-
. They can be used to synchronize execution across goroutines.
In this example, we are creating two goroutines that calculate the Fibonacci sequence and send the result to two channels. The range
keyword is used to iterate over the channel and print the result. The Close
function is used to close the channel when the goroutine is done sending the result, this is important because the range
keyword will keep on iterating over the channel until it is closed.
To explore more about channels, you can read more in my earlier article here.
Mutex
Mutex is another synchronization mechanism that is used to provide exclusive access to a resource. It is also provided by the sync
package.
In this program, we are creating a goroutine that increments the counter by 1. The Lock
function is used to lock the mutex and will block the goroutine untill the mutex is unlocked by the Unlock
function. I cover more about mutex in my article here.
Conclusion
In this article, we have learned about goroutines and how to handle synchronization problems using WaitGroup
, Channels
, and Mutex
. This is not all about goroutines, there are some advanced topics that we have not covered in this article linked below.