Skip to main content

Concurrency in Go - Part 1

·3 mins

Concurrency is one of the strongest tools in Go but how to use them? Well just put the keyword go before a function. It can’t be that easy, does it? Well, not really. Let’s see some concurrency patterns in Go.

Introduction #

Let’s write a simple code

package main 
import "fmt"

func main() {
    count("Hello")
}

func count(str string) {
    for i := 0; i < 5; i++ {
        fmt.Println(str)
    }
}

On running this, we get a simple output of

❯ go run "d:\Code\GolangProjects\go-cocurency-test\main.go"
Hello
Hello
Hello
Hello
Hello

Let’s follow the mentioned statement and spawn a goroutine using go the keyword

func main() {
    go count("Hello")
}

In the output, we get nothing, its empty???

❯ go run "d:\Code\GolangProjects\go-cocurency-test\main.go"

Well, its because it did spawn a new thread but no one told the main thread to wait for it. Let’s just add a sleep after the main function to let it wait for the goroutine to complete.

func main() {
    go count("Hello")
    time.Sleep(time.Second)
}

Now we can see all Hello getting printed properly

❯ go run "d:\Code\GolangProjects\go-cocurency-test\main.go"
Hello
Hello
Hello
Hello
Hello

Wait Group #

Now using sleep is ok but one can’t expect to know when the function will get over. What we can do is use the Sync package and make a wait group.

func main() {
	
	// to know how much time our function take
	now := time.Now()
    defer func() {
        fmt.Println(time.Since(now))
    }()

    var wg sync.WaitGroup
    
    wg.Add(1)

    names := []string{"Achintya", "Master Chief", "Solid Snake"}

	go func() {
        for _, name := range names {
            count(name)
        }
        wg.Done()
    }()
    wg.Wait()
}

func count(str string) {
    fmt.Println("Hello from ", str)
    time.Sleep(time.Second)
}

Lemme explain the code a little bit

  • Defer func is a function that will execute at the end of the main function.

  • We will initialise a variable wg which will help us track whether the function is complete and wait for it.

  • wg.Add(1) : It actually adds a delta of 1 to the waiting group and says to wait.

  • After the for loop, I used them wg.Done() to tell the runtime that the task of this anonymous function is complete. It will decrement the delta by 1.

  • In the end, we have wg.Wait(). It will wait and keep the main function running until the value of the delta becomes 0.

  • Also, I have added a time.Sleep() count function so as to make it feel like a real-world scenario.

Now the output would be here as

❯ go run "d:\Code\GolangProjects\go-cocurency-test\main.go"
Hello from  Achintya
Hello from  Master Chief
Hello from  Solid Snake
3.0329866s

And our code is working fine but we are just spawning 1 thread. How about spawning a new thread for every iteration of for loop? We gotta take advantage of goroutines. Let’s change the code a little.

var wg sync.WaitGroup

func main() {
    now := time.Now()

    defer func() {
        fmt.Println(time.Since(now))
    }()

    names := []string{"Achintya", "Master Chief", "Solid Snake"}

    for _, name := range names {
        wg.Add(1)
        go count(name)
    }
    
    wg.Wait()
}

func count(str string) {
    fmt.Println("Hello from ", str)
    time.Sleep(time.Second)
    
    defer func ()  {
        wg.Done()
    }()
}

Here we made a new goroutine in each iteration of for loop. I have added wg.Add(1) and then completed the wg.Done() in the function call using defer. Now even if all the function is supposed to take 3 seconds, we through goroutines have completed in almost 1 second.

❯ go run "d:\Code\GolangProjects\go-cocurency-test\main.go"
Hello from  Solid Snake
Hello from  Achintya
Hello from  Master Chief
1.008593s

This will be part 1. In later parts, we will study about channels in Go.