Concurrency and Packages
Mutex Metrics
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
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)
}
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.WaitGroupwg ← sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr A⟩}, sema:0x0}
pass 1 of 214for 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}wg ← sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr B⟩}, sema:0x0}
pass 2 of 214for 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}wg.Wait()
26wgsync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr B⟩}, sema:0x0}.Wait()func()
pass 1 of 215wg.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++ {lock ← sync.Mutex{state:1, sema:0x0}, metrics["hits"] ← 1
pass 1 of 417defer 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 pass iwglockmetrics["hits"]1 0 — sync.Mutex{state:0, sema:0x0} → sync.Mutex{state:1, sema:0x0} 0 → 1 2 1 sync.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 3 0 — sync.Mutex{state:0, sema:0x0} → sync.Mutex{state:1, sema:0x0} 2 → 3 4 1 — sync.Mutex{state:0, sema:0x0} → sync.Mutex{state:1, sema:0x0} 3 → 4 func()
pass 2 of 215wg.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++ {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
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.WaitGroupwg ← sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr A⟩}, sema:0x0}
pass 1 of 214for 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}wg ← sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr B⟩}, sema:0x0}
pass 2 of 214for 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}wg.Wait()
26wgsync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr B⟩}, sema:0x0}.Wait()func()
pass 1 of 215wg.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++ {lock ← sync.Mutex{state:1, sema:0x0}, metrics["hits"] ← 1
pass 1 of 217defer 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}func()
pass 2 of 215wg.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++ {lock ← sync.Mutex{state:1, sema:0x0}, metrics["hits"] ← 2
pass 2 of 217defer 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}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
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.WaitGroupwg ← sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr A⟩}, sema:0x0}
pass 1 of 214for 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}wg ← sync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr B⟩}, sema:0x0}
pass 2 of 214for 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}wg.Wait()
26wgsync.WaitGroup{noCopy:sync.noCopy{}, state:atomic.Uint64{_:atomic.noCopy{}, _:atomic.align64{}, v:⟨addr B⟩}, sema:0x0}.Wait()func()
pass 1 of 215wg.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++ {lock ← sync.Mutex{state:1, sema:0x0}, metrics["hits"] ← 1
pass 1 of 617defer 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 pass iwglockmetrics["hits"]1 0 — sync.Mutex{state:0, sema:0x0} → sync.Mutex{state:1, sema:0x0} 0 → 1 2 1 — sync.Mutex{state:0, sema:0x0} → sync.Mutex{state:1, sema:0x0} 1 → 2 3 2 sync.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 4 0 — sync.Mutex{state:0, sema:0x0} → sync.Mutex{state:1, sema:0x0} 3 → 4 5 1 — sync.Mutex{state:0, sema:0x0} → sync.Mutex{state:1, sema:0x0} 4 → 5 6 2 — sync.Mutex{state:0, sema:0x0} → sync.Mutex{state:1, sema:0x0} 5 → 6 func()
pass 2 of 215wg.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++ {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