Go Tutorial · Concurrency · Lesson 23 of 30

Go Goroutines and WaitGroup

Run functions concurrently with go, and wait for them with sync.WaitGroup.

Goroutines

Put go in front of a function call and it runs concurrently in a goroutine — a lightweight thread managed by the Go runtime. You can start thousands of them cheaply.

Waiting with WaitGroup

main doesn't wait for goroutines. Use a sync.WaitGroup to wait until they're all done:

Example 1
package main

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

func main() {
	urls := []string{"a.example", "b.example", "c.example"}
	results := make([]string, len(urls))
	var wg sync.WaitGroup
	for i, u := range urls {
		wg.Add(1)
		go func() {
			defer wg.Done()
			time.Sleep(50 * time.Millisecond) // pretend to fetch
			results[i] = u + ": 200 OK"
		}()
	}
	wg.Wait()
	for _, r := range results {
		fmt.Println(r)
	}
}

Output

a.example: 200 OK
b.example: 200 OK
c.example: 200 OK

Run this on your computer — see “Get Started” for setup.

Goroutines run in an unpredictable order, so this example writes each result into its own slot to keep the output stable.

Protecting shared data

When goroutines update shared data, protect it with a sync.Mutex (or use channels). The race detector — go run -race — finds unsafe access:

Example 2
package main

import (
	"fmt"
	"sync"
)

func main() {
	var (
		mu    sync.Mutex
		total int
		wg    sync.WaitGroup
	)
	for range 100 {
		wg.Add(1)
		go func() {
			defer wg.Done()
			mu.Lock()
			total++
			mu.Unlock()
		}()
	}
	wg.Wait()
	fmt.Println("total:", total)
}

Output

total: 100

Run this on your computer — see “Get Started” for setup.

Test yourself

Which keyword starts a goroutine?

  1. async
  2. go
  3. spawn
  4. thread
Show answer

Answer: go