Golang has become a popular language due to the features that allow developers to build incredibly fast applications using the power of concurrency programming. In this series of tutorials, we will learn about the concepts of Go Concurrency from the beginner's level.
This is the first article of the tutorial series, which contains the following:
In this article, we will focus on the use of GoRoutines. Before that, let me explain concurrency first. Normally, a large program is made up of multiple subprograms and these tasks will be executed in order or in a sequence without concurrency programming. However, if you can find a way to divide the entire application into several independent components and execute them out of order while still yielding the same results, you will benefit from using concurrency as it allows two or more tasks to progress simultaneously.
In Golang, concurrency is achieved by GoRoutines, which is just a function with the keyword "go" in front of it and it is capable of running concurrently with the other functions.
package main import "fmt" func main() { go print() fmt.Println("hello from main") } func print() { for i := 0; i < 5; i++ { fmt.Println("hello from goroutine") } }
You may notice that the code above only prints the message "hello from main" and not "hello from goroutine". This is because the main function itself is also a GoRoutine, which runs concurrently with our print function. In this case, the main GoRoutine will not wait for another GoRoutine to complete and it will execute the line fmt.Println("hello from main") immediately and exit before the print() function can print its message.
package main import ( "fmt" "time" ) func main() { go print() time.Sleep(1 * time.Second) fmt.Println("hello from main") } func print() { for i := 0; i < 5; i++ { fmt.Println("hello from goroutine") } }
In the second example, the main GoRoutine is forced to sleep for a second before printing the message "hello from main". Now, you will be able to see the messages from the print() function because it has sufficient time to do its job before the main function exits. In this example, you can treat the time.Sleep() function as a "blocker" which blocks the main GoRoutine for one second. Another potential "blocker" could be fmt.Scanln(). However, these are not a good idea in practice to control and synchronize the GoRoutines, particularly when the application contains multiple GoRoutines and when you need to have communication between GoRoutines. In future tutorials, the concepts of WaitGroup and Channels will be introduced to manage GoRoutines.
package main import ( "fmt" "time" ) func main() { go printCat() go printDog() time.Sleep(1 * time.Second) fmt.Println("hello from main") } func printCat() { for i := 0; i < 5; i++ { fmt.Println("Cat") } } func printDog(){ for i := 0; i < 5; i++ { fmt.Println("Dog") } }
Let's try to understand an example with multiple GoRoutines. In this third example, we have three GoRoutines. Two of them are explicit: printCat() and printDog(). The third one is the implicit main GoRoutine. By running this script, you may assume to see five "Cat" first, followed by five "Dog" printed in the terminal. However, this is not the case because both printCat() and printDog() are running concurrently and you will never know the order of the execution without the use of blocking concepts such as WaitGroup and Channels.
In the next tutorial, we will look into WaitGroups and understand how this can be tied together with GoRoutines in ensuring the results expected.
Stay tuned!