[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. Seefor more information.

關鍵詞:Goroutine

相關推薦:

Golang精編100題-搞定golang面試

Go 單元測試,基準測試,http 測試

Go語言的美好和醜陋

關於Go,你可能不注意的7件事

Go - the good, the bad and the ugly

go pprof 性能分析

Go 語法速覽與實踐清單(V0.5)

深入Golang之context

Go in 1 Hour

Golang 新手可能會踩的 50 個坑