Understanding Sync/Async processing and implementing it with Golang

Rahul Kapoor
3 min readNov 5, 2023

When working on projects that require handling multiple operations simultaneously, developers often encounter the choice between synchronous and asynchronous approaches.

Sync processing

In the synchronous approach, tasks are executed one after another in a sequential manner. Each task must be completed before the next one begins. This means that if a particular task takes a significant amount of time to complete, it can block the execution of subsequent tasks, resulting in potential performance bottlenecks.

A simple real-life example will be 2 people having a conversation over a beer. One person tells and asks something, the other person responds accordingly and vice-versa…

In our example below, each URL call must complete its entire request-response cycle and provide a response or error, so that subsequent URL calls can be made.

package main

import (
"fmt"
"net/http"
)

func makeUrlCall(url string) {
_, err := http.Get(url)
if err != nil {
fmt.Println("Got error in connecting to url: ", url)
}

fmt.Println("Got the response from our url: ", url)
}

func main() {

fmt.Println("Welcome here !!")
fmt.Println()

//slice of urls
urlSlice := []string{
"http://www.google.com",
"http://www.udemy.com",
"http://www.medium.com",
}

for idx, url := range urlSlice {
fmt.Println("Calling url on index: ", idx)
makeUrlCall(url)
}

fmt.Println()
fmt.Println("End of sync processing !!")

return
}

Response:

Welcome here !!

Calling url on index: 0
Got the response from our url: http://www.google.com
Calling url on index: 1
Got the response from our url: http://www.udemy.com
Calling url on index: 2
Got the response from our url: http://www.medium.com

End of sync processing !!

Async processing

In the asynchronous approach, tasks are executed independently and concurrently. This means that the program doesn’t wait for a task to complete before moving on to the next one. Asynchronous programming in Golang is achieved using Goroutines, lightweight threads managed by the Go runtime.

A simple real-life example will be going to a car repair shop. Once the engineers are done with processing other tasks they will pick and process your task. Meanwhile, you can do other important things till your car gets picked up and is repaired.

In our example below, each URL call will be made via goroutine; in its own thread and the response can be handled accordingly.

package main

import (
"fmt"
"net/http"
"sync"
)

func makeUrlCall(url string) {
_, err := http.Get(url)
if err != nil {
fmt.Println("Got error in connecting to url: ", url)
}

fmt.Println("Got the response from our url: ", url)
}

func main() {

fmt.Println("Welcome here !!")
fmt.Println()

//slice of urls
urlSlice := []string{
"http://www.google.com",
"http://www.udemy.com",
"http://www.medium.com",
}

var wg sync.WaitGroup

for _, u := range urlSlice {
wg.Add(1)
//all the url's to get error/response are called in their own separate thread via goroutines
go func(url string) {
defer wg.Done()

makeUrlCall(url)
}(u)
}

wg.Wait()

fmt.Println()
fmt.Println("End of sync processing !!")

return
}

Response:

Welcome here !!

Got the response from our url: http://www.udemy.com
Got the response from our url: http://www.google.com
Got the response from our url: http://www.medium.com

End of sync processing !!

If we compare the performance for both of our ways after adding few more URLs in our slice thus leading to few more HTTP get calls.

Sync processing took: 2.95 sec
Async processing took: 495 msec

--

--