A mutex protects a shared map when more than one goroutine records metrics.

protected metrics Lock the shared map only around the update, then read it after all goroutines have joined.

Mutex Metrics

hitsPerWorker
mutex_metrics.go
Replay: real traced execution (multi-file project)
package main

import (
	"fmt"
	"sync"
)

func main() {
	var hitsPerWorker = 2
	metrics := map[string]int{"hits": 0}
	var lock sync.Mutex
	var wg sync.WaitGroup

	for worker := 0; worker < 2; worker++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for i := 0; i < hitsPerWorker; i++ {
				lock.Lock()
				metrics["hits"] = metrics["hits"] + 1
				lock.Unlock()
			}
		}()
	}

	wg.Wait()

	fmt.Println("hitsPerWorker=", hitsPerWorker)
	fmt.Println("hits=", metrics["hits"])
	fmt.Println("workers=", 2)
}
package main

import (
	"fmt"
	"sync"
)

func main() {
	var hitsPerWorker = 1
	metrics := map[string]int{"hits": 0}
	var lock sync.Mutex
	var wg sync.WaitGroup

	for worker := 0; worker < 2; worker++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for i := 0; i < hitsPerWorker; i++ {
				lock.Lock()
				metrics["hits"] = metrics["hits"] + 1
				lock.Unlock()
			}
		}()
	}

	wg.Wait()

	fmt.Println("hitsPerWorker=", hitsPerWorker)
	fmt.Println("hits=", metrics["hits"])
	fmt.Println("workers=", 2)
}
package main

import (
	"fmt"
	"sync"
)

func main() {
	var hitsPerWorker = 3
	metrics := map[string]int{"hits": 0}
	var lock sync.Mutex
	var wg sync.WaitGroup

	for worker := 0; worker < 2; worker++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for i := 0; i < hitsPerWorker; i++ {
				lock.Lock()
				metrics["hits"] = metrics["hits"] + 1
				lock.Unlock()
			}
		}()
	}

	wg.Wait()

	fmt.Println("hitsPerWorker=", hitsPerWorker)
	fmt.Println("hits=", metrics["hits"])
	fmt.Println("workers=", 2)
}
  1. hitsPerWorker ← 2, metrics ← map[string]int{"hits":0}, lock ← sync.Mutex{state:0, sema:0x0}

    8func main() {9  var hitsPerWorker→ 2 = 2 //@hitsPerWorker=1, 310  metrics→ map[string]int{"hits":0} := map[string]int{"hits": 0}11  var lock→ sync.Mutex{state:0, sema:0x0} sync.Mutex12  var wg→ sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x0}, sema:0x0} sync.WaitGroup
  2. wg ← sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr A⟩}, sema:0x0}

    pass 1 of 2
    14for worker0 := 0; worker < 2; worker++ {15  wg→ sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr A⟩}, sema:0x0}.Add(1)16  go func() {17    defer wg.Done()18    for i := 0; i < hitsPerWorker; i++ {19      lock.Lock()20      metrics["hits"] = metrics["hits"] + 121      lock.Unlock()22    }23  }()24}
  3. wg ← sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr B⟩}, sema:0x0}

    pass 2 of 2
    14for worker1 := 0; worker < 2; worker++ {15  wg→ sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr B⟩}, sema:0x0}.Add(1)16  go func() {17    defer wg.Done()18    for i := 0; i < hitsPerWorker; i++ {19      lock.Lock()20      metrics["hits"] = metrics["hits"] + 121      lock.Unlock()22    }23  }()24}
  4. wg.Wait()

    26wgsync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr B⟩}, sema:0x0}.Wait()
  5. func()

    pass 1 of 2
    15wg.Add(1)16go func() {17  defer wgsync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr C⟩}, sema:0x0}.Done()18  for i := 0; i < hitsPerWorker; i++ {
  6. lock ← sync.Mutex{state:1, sema:0x0}, metrics["hits"] ← 1

    pass 1 of 4
    17defer wg.Done()18for i0 := 0; i < hitsPerWorker2; i++ {19  lock→ sync.Mutex{state:1, sema:0x0}.Lock()20  metrics["hits"]→ 1 = metrics["hits"] + 121  lock→ sync.Mutex{state:0, sema:0x0}.Unlock()22}
    All 4 passes — pass 1 is the card above
    passiwglockmetrics["hits"]
    10sync.Mutex{state:0, sema:0x0} sync.Mutex{state:1, sema:0x0}0 1
    21sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr D⟩}, sema:0x0}sync.Mutex{state:0, sema:0x0} sync.Mutex{state:1, sema:0x0}1 2
    30sync.Mutex{state:0, sema:0x0} sync.Mutex{state:1, sema:0x0}2 3
    41sync.Mutex{state:0, sema:0x0} sync.Mutex{state:1, sema:0x0}3 4
  7. func()

    pass 2 of 2
    15wg.Add(1)16go func() {17  defer wgsync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr D⟩}, sema:0x0}.Done()18  for i := 0; i < hitsPerWorker; i++ {
  8. wg ← sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x0}, sema:0x0}

    26  wg→ sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x0}, sema:0x0}.Wait()2728  fmt.Println("hitsPerWorker=", hitsPerWorker2)29  fmt.Println("hits=", metrics["hits"]4)30  fmt.Println("workers=", 2)31}
    outputhitsPerWorker= 2
    hits= 4
    workers= 2
  1. hitsPerWorker ← 1, metrics ← map[string]int{"hits":0}, lock ← sync.Mutex{state:0, sema:0x0}

    8func main() {9  var hitsPerWorker→ 1 = 110  metrics→ map[string]int{"hits":0} := map[string]int{"hits": 0}11  var lock→ sync.Mutex{state:0, sema:0x0} sync.Mutex12  var wg→ sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x0}, sema:0x0} sync.WaitGroup
  2. wg ← sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr A⟩}, sema:0x0}

    pass 1 of 2
    14for worker0 := 0; worker < 2; worker++ {15  wg→ sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr A⟩}, sema:0x0}.Add(1)16  go func() {17    defer wg.Done()18    for i := 0; i < hitsPerWorker; i++ {19      lock.Lock()20      metrics["hits"] = metrics["hits"] + 121      lock.Unlock()22    }23  }()24}
  3. wg ← sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr B⟩}, sema:0x0}

    pass 2 of 2
    14for worker1 := 0; worker < 2; worker++ {15  wg→ sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr B⟩}, sema:0x0}.Add(1)16  go func() {17    defer wg.Done()18    for i := 0; i < hitsPerWorker; i++ {19      lock.Lock()20      metrics["hits"] = metrics["hits"] + 121      lock.Unlock()22    }23  }()24}
  4. wg.Wait()

    26wgsync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr B⟩}, sema:0x0}.Wait()
  5. func()

    pass 1 of 2
    15wg.Add(1)16go func() {17  defer wgsync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr C⟩}, sema:0x0}.Done()18  for i := 0; i < hitsPerWorker; i++ {
  6. lock ← sync.Mutex{state:1, sema:0x0}, metrics["hits"] ← 1

    pass 1 of 2
    17defer wg.Done()18for i0 := 0; i < hitsPerWorker1; i++ {19  lock→ sync.Mutex{state:1, sema:0x0}.Lock()20  metrics["hits"]→ 1 = metrics["hits"] + 121  lock→ sync.Mutex{state:0, sema:0x0}.Unlock()22}
  7. func()

    pass 2 of 2
    15wg.Add(1)16go func() {17  defer wgsync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr D⟩}, sema:0x0}.Done()18  for i := 0; i < hitsPerWorker; i++ {
  8. lock ← sync.Mutex{state:1, sema:0x0}, metrics["hits"] ← 2

    pass 2 of 2
    17defer wg.Done()18for i0 := 0; i < hitsPerWorker1; i++ {19  lock→ sync.Mutex{state:1, sema:0x0}.Lock()20  metrics["hits"]→ 2 = metrics["hits"] + 121  lock→ sync.Mutex{state:0, sema:0x0}.Unlock()22}
  9. wg ← sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x0}, sema:0x0}

    26  wg→ sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x0}, sema:0x0}.Wait()2728  fmt.Println("hitsPerWorker=", hitsPerWorker1)29  fmt.Println("hits=", metrics["hits"]2)30  fmt.Println("workers=", 2)31}
    outputhitsPerWorker= 1
    hits= 2
    workers= 2
  1. hitsPerWorker ← 3, metrics ← map[string]int{"hits":0}, lock ← sync.Mutex{state:0, sema:0x0}

    8func main() {9  var hitsPerWorker→ 3 = 310  metrics→ map[string]int{"hits":0} := map[string]int{"hits": 0}11  var lock→ sync.Mutex{state:0, sema:0x0} sync.Mutex12  var wg→ sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x0}, sema:0x0} sync.WaitGroup
  2. wg ← sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr A⟩}, sema:0x0}

    pass 1 of 2
    14for worker0 := 0; worker < 2; worker++ {15  wg→ sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr A⟩}, sema:0x0}.Add(1)16  go func() {17    defer wg.Done()18    for i := 0; i < hitsPerWorker; i++ {19      lock.Lock()20      metrics["hits"] = metrics["hits"] + 121      lock.Unlock()22    }23  }()24}
  3. wg ← sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr B⟩}, sema:0x0}

    pass 2 of 2
    14for worker1 := 0; worker < 2; worker++ {15  wg→ sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr B⟩}, sema:0x0}.Add(1)16  go func() {17    defer wg.Done()18    for i := 0; i < hitsPerWorker; i++ {19      lock.Lock()20      metrics["hits"] = metrics["hits"] + 121      lock.Unlock()22    }23  }()24}
  4. wg.Wait()

    26wgsync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr B⟩}, sema:0x0}.Wait()
  5. func()

    pass 1 of 2
    15wg.Add(1)16go func() {17  defer wgsync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr C⟩}, sema:0x0}.Done()18  for i := 0; i < hitsPerWorker; i++ {
  6. lock ← sync.Mutex{state:1, sema:0x0}, metrics["hits"] ← 1

    pass 1 of 6
    17defer wg.Done()18for i0 := 0; i < hitsPerWorker3; i++ {19  lock→ sync.Mutex{state:1, sema:0x0}.Lock()20  metrics["hits"]→ 1 = metrics["hits"] + 121  lock→ sync.Mutex{state:0, sema:0x0}.Unlock()22}
    All 6 passes — pass 1 is the card above
    passiwglockmetrics["hits"]
    10sync.Mutex{state:0, sema:0x0} sync.Mutex{state:1, sema:0x0}0 1
    21sync.Mutex{state:0, sema:0x0} sync.Mutex{state:1, sema:0x0}1 2
    32sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr D⟩}, sema:0x0}sync.Mutex{state:0, sema:0x0} sync.Mutex{state:1, sema:0x0}2 3
    40sync.Mutex{state:0, sema:0x0} sync.Mutex{state:1, sema:0x0}3 4
    51sync.Mutex{state:0, sema:0x0} sync.Mutex{state:1, sema:0x0}4 5
    62sync.Mutex{state:0, sema:0x0} sync.Mutex{state:1, sema:0x0}5 6
  7. func()

    pass 2 of 2
    15wg.Add(1)16go func() {17  defer wgsync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr D⟩}, sema:0x0}.Done()18  for i := 0; i < hitsPerWorker; i++ {
  8. wg ← sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x0}, sema:0x0}

    26  wg→ sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:0x0}, sema:0x0}.Wait()2728  fmt.Println("hitsPerWorker=", hitsPerWorker3)29  fmt.Println("hits=", metrics["hits"]6)30  fmt.Println("workers=", 2)31}
    outputhitsPerWorker= 3
    hits= 6
    workers= 2