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




