The Currency of Concurrency in Golang

Concurrency in Golang is one of the most powerful built in tools Go has to offer. It's simplistic in it's implementation so it's easy for intermediate Gophers to understand. The three aspects of Golang we need to understand to take full advantage of concurrency are go routines, channels and wait groups. Let's start with Goroutines.

Goroutines

Let's first write an example Goroutine:


package main

import (
"fmt"
)

func twoPlusTwo() int {
four := 2 + 2
fmt.Println(four)
return four
}

func fourPlusFour() int {
eight := 4 + 4
fmt.Println(eight)
return eight
}

func main() {
go twoPlusTwo()
go fourPlusFour()
fmt.Println("Hello, playground")
}

Notice the methods twoPlusTwo and fourPlusFour have the word "go" in front of them when they're called. This signifies that they are coroutines and run concurrently along side the main() function, which automatically runs in it's own go routine. A curious question here is why didn't the print statements for twoPlusTwo and fourPlusFour return?

The reason it didn't return is the main go routine doesn't wait for twoPlusTwo and fourPlusFour to return before it finishes running it's code.

Wait Groups

This is where wait groups come into play. Let's add Waitgroups to the above code.


package main

import (
"fmt"
"sync"
)

func twoPlusTwo(wg *sync.WaitGroup) int {
defer wg.Done()
four := 2 + 2
fmt.Println(four)
return four
}

func fourPlusFour(wg *sync.WaitGroup) int {
defer wg.Done()
eight := 4 + 4
fmt.Println(eight)
return eight
}

func main() {
var wg sync.WaitGroup
wg.Add(1)
go twoPlusTwo(&wg)
wg.Add(1)
go fourPlusFour(&wg)
wg.Wait()
fmt.Println("Hello, playground")
}

What we've done here is import the "sync" Golang package which contains our WaitGroup functionality, set sync.WaitGroup to a variable, add a pointer to sync.WaitGroup as a parameter to twoPlusTwo and fourPlusFour, add a WaitGroup for each goroutine we're running before it's called and pass in the wg argument to each go routine when it was called.

What we've accomplished here is telling the main goroutine to wait until our concurrent goroutines have finished. We could technically stop here, but there is one more built in Golang tool that will make our use of concurrency even better.

Channels

Channels provides a way to send or recieve typed values. A channel can be written to and then it's value be sent out to using the "<-" operator. Another note about channels is they can be buffered... which if possible, I highly recommend you do to make an efficient use of memory. However, it's important that you are aware of the needed capacity of the channel to avoid deadlocking. Let's add channels to our code to quickly see how they work.


package main

import (
"fmt"
"sync"
)

type Value struct {
Value int
}

func twoPlusTwo(results chan Value, wg *sync.WaitGroup) {
defer wg.Done()
four := 2 + 2
v := Value{Value: four}
results <- v
}

func fourPlusFour(results chan Value, wg *sync.WaitGroup) {
defer wg.Done()
eight := 4 + 4
v := Value{Value: eight}
results <- v
}

func main() {
var wg sync.WaitGroup
var results = make(chan Value, 2)
wg.Add(1)
go twoPlusTwo(results, &wg)
wg.Add(1)
go fourPlusFour(results, &wg)
wg.Wait()
close(results)
values := []Value{}
for v := range results {
values = append(values, v)
}
fmt.Println(values)
fmt.Println("Hello, playground")
}

The first change we make here is create the struct of type Value, which just stores an integer in the Value field. This wasn't completely necessary for this simplistic of an example, but I wanted to demonstrate channels with struct types since it's such a popular use case. The second change we make is creating a buffered channel of type Value with a capacity of 2 and store it in the results variable. The third step is to remove the return values of twoPlusTwo and fourPlusFour, pass the results channel into each method as a parameter and send the result of each method to the results channel. The last step is to read the values saved in the results channel by creating an empty slice "values", ranging over the results channel appending each saved Value to the values slice and then printing the slice before the main goroutine returns. Voila! We've achieved an efficient and reusable example of concurrency.