A sync.WaitGroup lets main wait until a goroutine calls Done.

wait group A wait group counts active tasks so code can wait for them to finish before reading final results.

Wait Group

base
wait_group.go
Replay: real traced execution (multi-file project)
package main

import (
	"fmt"
	"sync"
)

func main() {
	var base = 4
	var wg sync.WaitGroup
	results := make(chan int, 1)

	wg.Add(1)
	go func() {
		defer wg.Done()
		results <- base * 2
	}()

	wg.Wait()
	value := <-results
	fmt.Println("base=", base)
	fmt.Println("value=", value)
	fmt.Println("complete=", true)
}
package main

import (
	"fmt"
	"sync"
)

func main() {
	var base = 2
	var wg sync.WaitGroup
	results := make(chan int, 1)

	wg.Add(1)
	go func() {
		defer wg.Done()
		results <- base * 2
	}()

	wg.Wait()
	value := <-results
	fmt.Println("base=", base)
	fmt.Println("value=", value)
	fmt.Println("complete=", true)
}
package main

import (
	"fmt"
	"sync"
)

func main() {
	var base = 6
	var wg sync.WaitGroup
	results := make(chan int, 1)

	wg.Add(1)
	go func() {
		defer wg.Done()
		results <- base * 2
	}()

	wg.Wait()
	value := <-results
	fmt.Println("base=", base)
	fmt.Println("value=", value)
	fmt.Println("complete=", true)
}
  1. base ← 4, wg ← sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x0}, sema:0x0}

    8func main() {9  var base→ 4 = 4 //@base=2, 610  var wg→ sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x0}, sema:0x0} sync.WaitGroup11  results→ (chan int)(⟨addr A⟩) := make(chan int, 1)1213  wg→ sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr B⟩}, sema:0x0}.Add(1)14  go func() {15    defer wg.Done()16    results <- base * 217  }()1819  wgsync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr B⟩}, sema:0x0}.Wait()20  value := <-results
  2. func()

    13wg.Add(1)14go func() {15  defer wgsync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr C⟩}, sema:0x0}.Done()16  results(chan int)(⟨addr A⟩) <- base4 * 217}()
  3. wg ← sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x0}, sema:0x0}

    19  wg→ sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x0}, sema:0x0}.Wait()20  value→ 8 := <-results(chan int)(⟨addr A⟩)21  fmt.Println("base=", base4)22  fmt.Println("value=", value8)23  fmt.Println("complete=", true)24}
    outputbase= 4
    value= 8
    complete= true
  1. base ← 2, wg ← sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x0}, sema:0x0}

    8func main() {9  var base→ 2 = 210  var wg→ sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x0}, sema:0x0} sync.WaitGroup11  results→ (chan int)(⟨addr A⟩) := make(chan int, 1)1213  wg→ sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr B⟩}, sema:0x0}.Add(1)14  go func() {15    defer wg.Done()16    results <- base * 217  }()1819  wgsync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr B⟩}, sema:0x0}.Wait()20  value := <-results
  2. func()

    13wg.Add(1)14go func() {15  defer wgsync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr C⟩}, sema:0x0}.Done()16  results(chan int)(⟨addr A⟩) <- base2 * 217}()
  3. wg ← sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x0}, sema:0x0}

    19  wg→ sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x0}, sema:0x0}.Wait()20  value→ 4 := <-results(chan int)(⟨addr A⟩)21  fmt.Println("base=", base2)22  fmt.Println("value=", value4)23  fmt.Println("complete=", true)24}
    outputbase= 2
    value= 4
    complete= true
  1. base ← 6, wg ← sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x0}, sema:0x0}

    8func main() {9  var base→ 6 = 610  var wg→ sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x0}, sema:0x0} sync.WaitGroup11  results→ (chan int)(⟨addr A⟩) := make(chan int, 1)1213  wg→ sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr B⟩}, sema:0x0}.Add(1)14  go func() {15    defer wg.Done()16    results <- base * 217  }()1819  wgsync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr B⟩}, sema:0x0}.Wait()20  value := <-results
  2. func()

    13wg.Add(1)14go func() {15  defer wgsync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr C⟩}, sema:0x0}.Done()16  results(chan int)(⟨addr A⟩) <- base6 * 217}()
  3. wg ← sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x0}, sema:0x0}

    19  wg→ sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x0}, sema:0x0}.Wait()20  value→ 12 := <-results(chan int)(⟨addr A⟩)21  fmt.Println("base=", base6)22  fmt.Println("value=", value12)23  fmt.Println("complete=", true)24}
    outputbase= 6
    value= 12
    complete= true

Follow the Wait

  1. base starts at 4.
  2. wg.Add(1) records one goroutine to wait for.
  3. The goroutine sends base * 2, so it sends 8 into results.
  4. defer wg.Done() marks the goroutine finished.
  5. wg.Wait() returns, then main reads 8 and prints complete= true. | base | goroutine sends | main reads | final lines | | ---: | ---: | ---: | --- | | 2 | 4 | 4 | base= 2; value= 4; complete= true | | 4 | 8 | 8 | base= 4; value= 8; complete= true | | 6 | 12 | 12 | base= 6; value= 12; complete= true |

Exercise: wait_group.go

Reproduce base= 4, value= 8, and complete= true, then use base 2 and 6 to predict value= 4 and value= 12.