Before getting started with GO
, WHY GO
? Do I hate python, it's a BIG NO.
There are some perks, ease of adoption, and responsibilities as we consider microservice architecture mainly in INFRA design and automation.
Merits of GO alongside Python.
GO solved two major problems,
- Ease of programming as an interpreted language like Python.
- With efficiency and safety as a static language like C++.
- Competitive multi-core computing as built-in support
Concurrency. This blog will focus only concurrency speciality of GO.
Concurrency in other languages comes as extended functionality unlike in GO we have built-in support also known as
goroutines
. Goroutines are deeply integrated with Go's runtime, this runtime engine takes care of managing the threads (blocking & unblocking)The runtime and the logic of a goroutine work together. Goroutines can communicate with one another and synchronize their execution.
- In Go, one of the synchronization elements is called a channel. Channel helps to share data between goroutines
To learn more about concurrency in python: dev.to/kcdchennai/optimising-python-workloa..
To run a function as a goroutine, call that function prefixed with the go statement.
sum() // A normal function call that executes sum synchronously and waits for completing it
go sum() // A goroutine that executes sum asynchronously and doesn't wait for completing it
Lets the learn this concurrency in GO using an example.
Using Opensource API endpoint here worldtimeapi.org/pages/examples, this API helps us with timezone using various params, we are using this to find timezone based on IP.
package main
import (
"fmt"
"io"
"log"
"net/http"
"os"
)
func request(url string) {
res, err := http.Get(url)
if err != nil {
panic(err)
}
defer res.Body.Close()
b, err := io.ReadAll(res.Body)
fmt.Println(string(b))
}
func main() {
if len(os.Args) < 2 {
log.Fatalln("Usage: go run main.go <url1> <url2> ... <urln>")
}
for _, url := range os.Args[1:] {
request("http://" + url)
}
}
Below is the response to my above excerpt. I have passed 4 args to the above function.
What is happening in the above program is Sequential execution
We sent a request to the first argument and when it completes, a response comes in. Then the program returns to the for loop and sends another request to the next argument, and the process continues.
Let's do concurrency using goroutines
now
GOroutines
GOroutines are managed by GO runtime scheduler which takes care managing threads accessing CPU.
asynchronous, powerful
As we already know to make fn into goroutine, add a prefix go behind the fn invocation. Updating the code below
package main
import (
"fmt"
"io"
"log"
"net/http"
"os"
)
func request(url string) {
res, err := http.Get(url)
if err != nil {
panic(err)
}
defer res.Body.Close()
b, err := io.ReadAll(res.Body)
fmt.Println(string(b))
}
func main() {
if len(os.Args) < 2 {
log.Fatalln("Usage: go run main.go <url1> <url2> ... <urln>")
}
for _, url := range os.Args[1:] {
go request("http://" + url)
}
}
The above program will result in below, which in 0.843 seconds.
:| why? Yes, we have started the concurrency and it triggered the fn 4 times but did not wait for the output. In order to visualize this let us add a waitgroup
WaitGroup
WaitGroup is included in the Golang sync package. It includes features that allow it to block and wait for any number of goroutines to complete their execution.
Code with added wait:
package main
import (
"fmt"
"io"
"log"
"net/http"
"os"
"sync"
)
var wg sync.WaitGroup
func request(url string) {
defer wg.Done()
res, err := http.Get(url)
if err != nil {
panic(err)
}
defer res.Body.Close()
b, err := io.ReadAll(res.Body)
fmt.Println(string(b))
}
func main() {
if len(os.Args) < 2 {
log.Fatalln("Usage: go run main.go <url1> <url2> ... <urln>")
}
for _, url := range os.Args[1:] {
go request("http://" + url)
wg.Add(1)
}
wg.Wait()
}
While executing the same fn with 8 args, the response was within 0.853s which is 4x faster than the normal way.
Hope you enjoyed this new exploration. We will discuss more about Go in my upcoming blog posts.
Peace!