liveBook · Manning

WaitGroup

As explained in Go in Action, Second Edition

WaitGroup is a Go synchronization type that counts active operations and blocks until its counter reaches zero.

A fixed delay cannot reliably tell a Go program when its goroutines have finished. A sync.WaitGroup can: it tracks outstanding operations, and Wait blocks until its counter reaches zero. Use it when a caller must not proceed before a group of concurrent tasks completes. section 9.2.1

For a collection of feed URLs:

  • Call Add(1) before launching each goroutine. Inside it, defer Done() so completion decrements the counter. section 9.2.1
  • Call Wait() before returning from the coordinating function. The newer WaitGroup.Go method offers another way to start tracked functions; Add and Done remain common in existing code. section 9.2.1
Listing 9.7 main.go: Concurrency with the go keyword with a WaitGroup
import (
 ...
 "sync"
 ...
)

...

func fetchFeeds(urls []string) {
 var wg sync.WaitGroup
 for _, url := range urls {
  wg.Add(1) #1
  go func(url string) {
   defer wg.Done() #2
   printFeedTitle(url)
  }(url)
 }
 fmt.Println("waiting....")
 wg.Wait() #3
}

func main() {
 fetchFeeds([]string{
  "https://rss.nytimes.com/services/xml/rss/nyt/World.xml",
  "https://feeds.bbci.co.uk/news/rss.xml",
 })
 fmt.Println("stopped.")
}

The feed-fetching function shows the counter being incremented before each launch, decremented on completion, and awaited before the function returns. section 9.2.1

A WaitGroup tracks completion, not results or errors. In fan-out/fan-in, feed-fetching goroutines send articles or errors through a results channel. A separate goroutine waits for every producer before closing that channel; the handler can then range over it to aggregate results. Keep error collection separate from completion tracking. section 9.2.1 section 9.5.2

Listing 9.23 main.go: New handler for fan-out/fan-in call
func (a *App) syncHandler(w http.ResponseWriter, r *http.Request) {

 // Fan-out: launch goroutines for each feed
 type result struct {
    articles []*gofeed.Item
    err      error
 }
 resultsChan := make(chan result, len(feeds)) #1
 var wg sync.WaitGroup #2

 for _, feedURL := range feeds {
    wg.Add(1)
    go func(url string) {
     defer wg.Done()
     ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
     defer cancel()

     fetchedArticles, err := a.fetchFeed(ctx, url)
     resultsChan <- result{articles: fetchedArticles, err: err} #3
    }(feedURL)
 }

 // Close results channel after all goroutines complete
 go func() {
    wg.Wait()
    close(resultsChan)
 }() #4

The HTTP handler shows Wait controlling when a shared results channel is closed, rather than attempting to carry the results itself. section 9.5.2

How Go in Action, Second Edition covers it

How deep it goes
Works through counter-based waiting, Go, and Add/Done/Wait patterns, but leaves error collection to other mechanisms.
Examples
Uses concurrent RSS feed fetching and an HTTP handler that aggregates articles and errors.
What it assumes
Assumes familiarity with Go functions and goroutines while explaining WaitGroup’s counter and coordination pattern.
What you can do after
Apply it via sync.WaitGroup to await goroutines and close a results channel after its producers finish.
How it connects
Connects goroutines to channels and fan-out/fan-in, with errgroup identified as another way to track concurrent work.

Also explained in

Compare how 5 books explain WaitGroup →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Go in Action, Second Edition. The section links point to the parts of the book it draws on.