My first GO! program. Letsss G0!!!

My first GO! program. Letsss G0!!!

Concurrency in GO

Jothimani Radhakrishnan's photo
Jothimani Radhakrishnan
·Jul 4, 2022·

4 min read

Table of contents

  • Merits of GO alongside Python.
  • GOroutines

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.

Screenshot 2022-07-04 at 3.28.22 PM.png

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.

Screenshot 2022-07-04 at 5.18.52 PM.png

:| 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()
}

Screenshot 2022-07-04 at 5.47.11 PM.png

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!

Did you find this article valuable?

Support Jothimani Radhakrishnan by becoming a sponsor. Any amount is appreciated!

Learn more about Hashnode Sponsors
 
Share this