How far along are you with your learning of Golang? You will inevitably encounter Golang concurrency with goroutine. If case you are already there and struggling, this post will make understanding goroutines painless.
Goroutines Enable Concurrency
The concept of concurrency is different from Parallel programming, and Golang can enable both or either via goroutines. To keep this post simple, let us stick with concurrency. Goroutines allow our codes to perform multiple tasks virtually simultaneously (but not entirely) using one CPU processor or a limited number of processors. With concurrency, each task takes turns in using the processor to complete its task. Meaning, at any one point, the processor only processes a single task. However, the processing happens so quickly that it makes us believe the processor works on multiple tasks simultaneously.
The Main Function is the main goroutine
The Golang main function that we are well familiar with is a goroutine. Moreover, it is the main goroutine in an application. Does it mean other functions are also goroutines? Although Golang can turn a function into a goroutine for concurrency, not precisely. Normal functions run sequentially. For instance, when we call two functions, the first function completes its operation before the second function can start running.
1 2 3 4 5 6 7 | func function1() {} func function2() {} func main() { function1() function2() // Will run only when function1 finishes its operation } |
Therefore, there is no concurrency in Golang codes without goroutines. How do we turn an ordinary function into a goroutine, then? The answer is simple, but it will not help us create a proper goroutine. So, the answer is to use the keyword go before a function call. For example, consider the following codes.
1 2 3 4 | func main() { go function1() go function2() } |
Goroutines Are Usually Functions
There is a way to group codes, and we can do that in Golang using functions. Each function could represent a unit of work that is repeatable and reusable. Therefore, functions (including anonymous functions) are perfect candidates for goroutines.
Using The go Keyword Is Not Enough
One problem is naively using the go keyword to turn a normal Golang function into a goroutine to achieve concurrency. Doing so, as a result, causes both the main function and the naive goroutine to run concurrently. However, the main function usually completes first before the goroutine even starts. For example, consider the following codes that we modified a bit.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | package main import "fmt" func function1() { fmt.Println("Function 1 is running") } func function2() { fmt.Println("Function 2 is running") } func main() { fmt.Println("Main function just started running") go function1() go function2() fmt.Println("Main function is done") } |
When we run the codes, we consistently get the following output.
1 2 | Main function just started running Main function is done |
How do we fix it?
Control Golang goroutine Concurrency With Channels
Use channels to control goroutines in Golang and synch up the concurrency. Channels allow the main function to communicate with the goroutines and vice versa. For example, consider the following codes that we modified to use a channel.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | package main import "fmt" func function1(channel chan string) { fmt.Println("Function 1 is running") channel <- "Function 1 is done" } func function2(channel chan string) { fmt.Println("Function 2 is running") channel <- "Function 2 is done" } func main() { fmt.Println("Main function just started running") // Create a channel channel := make(chan string) // Communicate with first goroutine by passing the channel go function1(channel) // Communicate with second goroutine by passing the channel go function2(channel) // Wait for feedback from one of the goroutines fmt.Println(<-channel) // Wait for feedback from which ever goroutine finishes last fmt.Println(<-channel) fmt.Println("Main function is done") } |
When we run the codes, we get the following results.
1 2 3 4 5 6 | Main function just started running Function 2 is running Function 2 is done Function 1 is running Function 1 is done Main function is done |
We tested the codes using version go1.17.8 for windows/amd64.