On Goroutines

Isuru SIriwardana
2 min readDec 23, 2023

Sequential programming, while familiar, often allows us for a way to handle multiple tasks simultaneously without losing clarity or control. However, Goroutines, the Go’s approach to concurrency, helps us avoid tasks stacking up, patiently waiting for their turn, while the program’s potential wastes away unused. Think of Goroutines as lightweight agents within your program, quick and independent, capable of executing concurrently with the main thread.

Here’s what makes Go routines so powerful in my view:

  • Minimal resource footprint: Their minimal memory footprint enables you to spawn hundreds or even thousands of routines without overwhelming system resources.
  • Effortless creation: Introducing the go keyword before a function instantly transforms it into a concurrent worker, streamlining the process of initiating parallel tasks.
  • Structured synchronisation: Go provides elegant mechanisms like channels for seamless communication and data exchange between routines, preventing chaos and ensuring coordinated execution.

Let’s explore a practical example. Imagine your program needs to download several large files. Traditionally, you’d be stuck waiting for each download to complete before starting the next, a scenario ripe for inefficiency.

  1. Define your download function: Encapsulate the download logic in a reusable function like downloadFile.
  2. Deploy parallel routines: Leverage go to launch a separate routine for each file download, creating a team of miniature downloaders working simultaneously.
  3. Harness channels for orchestration: Utilize channels to signal completion and collect downloaded data from each routine, ensuring orderly progress and data integrity.
  4. Join the symphony: Once all routines have finished their tasks, synchronise their completion using a wait group, allowing your main program to proceed gracefully.

Code example:

package main

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

func downloadFile(url string, wg *sync.WaitGroup) {
defer wg.Done()

resp, err := http.Get(url)
if err != nil {
fmt.Println("Error downloading:", err)
return
}
defer resp.Body.Close()

data, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("Error reading file:", err)
return
}

fmt.Printf("Downloaded: %s (%d bytes)\n", url, len(data))
}

func main() {
var wg sync.WaitGroup

files := []string{
"https://example.com/file1.zip",
"https://example.com/file2.pdf",
"https://example.com/file3.txt",
}

for _, url := range files {
wg.Add(1)
go downloadFile(url, &wg)
}

wg.Wait()

fmt.Println("All files downloaded successfully!")
}

Here’s a breakdown of key points for clarity,

  • go keyword: Turns a function call into a concurrent Go routine.
  • defer: Schedules a function call to be executed just before the enclosing function returns.
  • sync.WaitGroup: Synchronises concurrent routines by tracking their completion.
  • http.Get: Downloads a file from a URL.
  • ioutil.ReadAll: Reads the entire contents of a file or response body.

With Goroutines, you transition from a single-threaded app to a dynamic app, where tasks operate in harmonious parallel, significantly reducing execution time and boosting overall app responsiveness.

--

--