Lesson 14 / 26

Goroutines and WaitGroups

Thousands of lightweight tasks.

go f() and waiting for completion

A goroutine is a lightweight concurrent function started with go f(); the runtime multiplexes many goroutines onto a few OS threads, so thousands are cheap. The program does not wait for goroutines automatically: use a sync.WaitGroup (Go 1.25 added wg.Go(func(){...}), which handles Add and Done for you) or channels to wait. Give each goroutine its own data or synchronise access; since Go 1.22 each loop iteration has its own copy of the loop variable, fixing a classic bug.

Goroutines and channels

Go makes concurrency cheap with goroutines, coordinates it with channels and context, and checks it with the race detector.

Four ideas: goroutines, channels, select and context, races and mutexes.
Figure 5.1 — Goroutines, channels, context and races.

Five concurrent fetches, run

I ran this with Go 1.27.1 (go run . in a module named demo, standard library only). Five simulated 100 ms calls run concurrently, each writing its own slot of the results slice, and finish in well under the 500 ms a sequential loop would need.

package main

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

func fetch(id int) string {
	time.Sleep(100 * time.Millisecond) // pretend network call
	return fmt.Sprintf("result-%d", id)
}

func main() {
	start := time.Now()
	results := make([]string, 5)
	var wg sync.WaitGroup
	for i := range 5 {
		wg.Go(func() { // Go 1.25+: WaitGroup.Go adds, runs and marks done
			results[i] = fetch(i) // each goroutine writes its own index
		})
	}
	wg.Wait()
	fmt.Println(results)
	fmt.Println("took under 300ms:", time.Since(start) < 300*time.Millisecond)
}

Output:

[result-0 result-1 result-2 result-3 result-4]
took under 300ms: true

Bound concurrency

For many tasks, limit parallelism (a worker pool or a semaphore channel) so you do not overwhelm downstream services.

Quick check: Does main wait for goroutines it started before exiting?

  • Only if they print output
  • Yes, always
  • Only for the first goroutine
  • No, you must wait explicitly, for example with a WaitGroup
Answer

No, you must wait explicitly, for example with a WaitGroup — Coordinate completion explicitly.