[Golang] Use Defer to Wait For Goroutines to Finish


This post shows how to use defer statement to elegantly wait for all goroutines to finish.

Let's start with bad practice. Look at the following code example, which waits for all goroutines to finish:

Run Code on Go Playground

package main

import (
      "fmt"
)

func routine(site string, c chan int) {
      // do something
      fmt.Println(site)

      c <- 1
}

func main() {
      c := make(chan int)

      sites := []string{
              "https://www.google.com/",
              "https://duckduckgo.com/",
              "https://www.bing.com/"}

      for _, site := range sites {
              go routine(site, c)
      }

      // wait all goroutines to finish
      for i := 0; i < len(sites); i++ {
              <-c
      }
}

In the end of the goroutine, The line c <- 1 sends integer 1 to channel to notify that the goroutine is finished. This way looks good, if your goroutine return only at the end of the func. More often than not, the goroutine will return at multiple places inside the func. For example, consider the following goroutine:

func routine(c chan int) {
      b, err := ioutil.ReadFile("myfile.txt")
      if err != nil {
              fmt.Println(err)
              c <- 1
              return
      }

      mystruct := MyStruct{}
      err = json.Unmarshal(b, &mystrcut)
      if err != nil {
              fmt.Println(err)
              c <- 1
              return
      }

      fmt.Println(mystruct)
      c <- 1
      return
}

As you can see from above example, there are three return statement in the goroutine, and you have to write c <- 1 three times right before the return. This is not a good practice because it is easy to forget to write c <- 1 if you add more return for handling more possible errors in the code.

A good practice and more elegant way to do this is to use defer statement. According to the description in A Tour of Go:

A defer statement defers the execution of a function until the surrounding function returns.

So we can use defer to write c <- 1 only once as follows:

func routine(c chan int) {
      defer func() { c <- 1 }()

      b, err := ioutil.ReadFile("myfile.txt")
      if err != nil {
              fmt.Println(err)
              return
      }

      mystruct := MyStruct{}
      err = json.Unmarshal(b, &mystrcut)
      if err != nil {
              fmt.Println(err)
              return
      }

      fmt.Println(mystruct)
      return
}

No matter how many return in your goroutine, the c <- 1 is guaranteed to be executed right after the function returns. To use defer is a better practice because it makes your code more readable and you will not forget to add c <- 1 if you add more return in the function.

The following is complete code example of good practice:

Run Code on Go Playground

package main

import (
      "fmt"
)

func routine(site string, c chan int) {
      defer func() { c <- 1 }()

      // do something
      fmt.Println(site)
}

func main() {
      c := make(chan int)

      sites := []string{
              "https://www.google.com/",
              "https://duckduckgo.com/",
              "https://www.bing.com/"}

      for _, site := range sites {
              go routine(site, c)
      }

      // wait all goroutines to finish
      for i := 0; i < len(sites); i++ {
              <-c
      }
}

In the example of this post, we use channel to wait for all goroutines to finish, you can also use sync.WaitGroup to do this. See [3] for more information.


Tested on: The Go Playground


References:

[1]Defer - A Tour of Go
[2]Defer, Panic, and Recover - The Go Blog
[3][Golang] Wait For Goroutine to Finish
[4]goroutine anti-pattern? : golang
[5]Behavior of defer function in named return function : golang
[6]Non blocking way of notifying a goroutine, by many at once : golang