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.