[go async] WaitGroup

Intro

As we know, according of a tour of Go, a goroutine is a lightweight thread managed by the Go runtime.

One of a common problems with these goroutines - waiting for ending of all children goroutines in a main goroutine before end.

Look on this code that simulate same useful work:

package main

import (
	"fmt"
	"time"
)

func consumer1() {
	fmt.Println("[consumer 1] Strat some work")
	time.Sleep(2 * time.Second)
	fmt.Println("[consumer 1] End some work")
}

func consumer2() {
	fmt.Println("[consumer 2] Strat some work")
	time.Sleep(1 * time.Second)
	fmt.Println("[consumer 2] End some work")
}

func runConsumers() {
	go consumer1()
	go consumer2()
}

func main() {
	runConsumers()
}

Try to run it and your got an empty result:

go run main.go

It’s happened because a main goroutine end of work before children goroutines end of own works.

WaitGroup

For solver this problem, Go has a standard library primitive WaitGroup from package sync.

How it work

The main goal of whit primitive is:

  • increase goroutine counter while your adding new goroutine;
  • decrease goroutine counter when goroutine done his job;
  • and blocks execution as long as this counter is greater than zero.

Three method that WaitGroup has for done this job:

  • Add(int): increases WaitGroup goroutine count by given integer value.
  • Done(): decreases WaitGroup goroutine counter by 1, that indicate that termination of a goroutine.
  • Wait(): block execution as long as the WaitGroup goroutine counter is greater than zero.

Example

Let’s use WaitGroup for modify our example:

package main

import (
	"fmt"
	"sync"
	"time"
)

func consumer1(wg *sync.WaitGroup) {
	defer wg.Done()
	fmt.Println("[consumer 1] Strat some work")
	time.Sleep(2 * time.Second)
	fmt.Println("[consumer 1] End some work")
}

func consumer2(wg *sync.WaitGroup) {
	defer wg.Done()
	fmt.Println("[consumer 2] Strat some work")
	time.Sleep(1 * time.Second)
	fmt.Println("[consumer 2] End some work")
}

func runConsumers() {
	wg := new(sync.WaitGroup)

	// Add 2 goroutine
	wg.Add(2)

	// Run goroutines
	go consumer1(wg)
	go consumer2(wg)

	// Waiting for counter=0
	wg.Wait()
}

func main() {
	runConsumers()
}

Try to run it - we got result:

go run main.go
[consumer 2] Strat some work
[consumer 1] Strat some work
[consumer 2] End some work
[consumer 1] End some work

Conclusions

Use sync.WaitGroup if you need to run children goroutines and wait in main goroutine until children goroutines will done own job.