Goroutines, WaitGroup, Mutex & Atomics

Starting concurrent work with go, waiting for it, and protecting shared data.

A goroutine is a function running concurrently, started with the go keyword. Goroutines are very cheap — a few kilobytes of stack each — and the Go runtime schedules them onto operating-system threads, so programs can run hundreds of thousands of them.

Goroutines in practice

wg.Add(1) before each go, defer wg.Done() inside, wg.Wait() to wait for all of them.

Each goroutine writes its own results[i] element, so no locking is needed there.

for i := range 4 (Go 1.22+) loops 0..3; since Go 1.22 each iteration has its own i, so closures capture the right value.

A Mutex serializes the shared counter; atomic.Int64 does the same for a single number without a lock.

100,000 goroutines start and finish easily.

Finding data races

go func() { counter++ }() // from many goroutines Protect counter with a sync.Mutex, or use atomic.Int64

Unsynchronized concurrent writes are a data race: increments are lost and the result is unpredictable.

Why must wg.Add be called before starting the goroutine, not inside it?

Otherwise wg.Wait could run before the goroutine has called Add, see a zero counter and return too early.