Includes solved 02-worker-pool and 09-safe-counter (mutex, atomic, sharded). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GbnJj9ZqEx9nUvzkjdwfMq
130 lines
2.5 KiB
Go
130 lines
2.5 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
"testing"
|
|
)
|
|
|
|
const (
|
|
goroutines = 16
|
|
perG = 1000
|
|
)
|
|
|
|
func TestMutexCounter(t *testing.T) {
|
|
c := NewMutexCounter()
|
|
var wg sync.WaitGroup
|
|
for g := 0; g < goroutines; g++ {
|
|
wg.Add(1)
|
|
go func(g int) {
|
|
defer wg.Done()
|
|
for i := 0; i < perG; i++ {
|
|
c.Inc(fmt.Sprintf("k%d", i%4))
|
|
}
|
|
}(g)
|
|
}
|
|
wg.Wait()
|
|
var total int64
|
|
for i := 0; i < 4; i++ {
|
|
total += c.Get(fmt.Sprintf("k%d", i))
|
|
}
|
|
if want := int64(goroutines * perG); total != want {
|
|
t.Fatalf("lost updates: want %d got %d", want, total)
|
|
}
|
|
}
|
|
|
|
func TestSnapshotIsACopy(t *testing.T) {
|
|
c := NewMutexCounter()
|
|
c.Inc("a")
|
|
snap := c.Snapshot()
|
|
if snap == nil || snap["a"] != 1 {
|
|
t.Fatalf("bad snapshot: %v", snap)
|
|
}
|
|
snap["a"] = 999 // mutating the snapshot must not affect the counter
|
|
if c.Get("a") != 1 {
|
|
t.Fatal("Snapshot returned the internal map, not a copy")
|
|
}
|
|
// And writing to the snapshot while counting must not race (-race checks).
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
go func() { defer wg.Done(); for i := 0; i < 100; i++ { c.Inc("a") } }()
|
|
for i := 0; i < 100; i++ {
|
|
snap["b"] = int64(i)
|
|
}
|
|
wg.Wait()
|
|
}
|
|
|
|
func TestAtomicCounter(t *testing.T) {
|
|
c := NewAtomicCounter()
|
|
var wg sync.WaitGroup
|
|
for g := 0; g < goroutines; g++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for i := 0; i < perG; i++ {
|
|
c.Inc()
|
|
}
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
if want := int64(goroutines * perG); c.Get() != want {
|
|
t.Fatalf("lost updates: want %d got %d", want, c.Get())
|
|
}
|
|
}
|
|
|
|
func TestShardedCounter(t *testing.T) {
|
|
c := NewShardedCounter()
|
|
var wg sync.WaitGroup
|
|
for g := 0; g < goroutines; g++ {
|
|
wg.Add(1)
|
|
go func(g int) {
|
|
defer wg.Done()
|
|
for i := 0; i < perG; i++ {
|
|
c.Inc(fmt.Sprintf("key-%d", i%50))
|
|
}
|
|
}(g)
|
|
}
|
|
wg.Wait()
|
|
var total int64
|
|
for k, v := range c.Snapshot() {
|
|
_ = k
|
|
total += v
|
|
}
|
|
if want := int64(goroutines * perG); total != want {
|
|
t.Fatalf("lost updates across shards: want %d got %d", want, total)
|
|
}
|
|
if c.Get("key-0") == 0 {
|
|
t.Fatal("Get by key broken")
|
|
}
|
|
}
|
|
|
|
func BenchmarkMutexCounter(b *testing.B) {
|
|
c := NewMutexCounter()
|
|
b.RunParallel(func(pb *testing.PB) {
|
|
for pb.Next() {
|
|
c.Inc("hot")
|
|
}
|
|
})
|
|
}
|
|
|
|
func BenchmarkShardedCounter(b *testing.B) {
|
|
c := NewShardedCounter()
|
|
keys := [8]string{"a", "b", "c", "d", "e", "f", "g", "h"}
|
|
b.RunParallel(func(pb *testing.PB) {
|
|
i := 0
|
|
for pb.Next() {
|
|
c.Inc(keys[i&7])
|
|
i++
|
|
}
|
|
})
|
|
}
|
|
|
|
func BenchmarkAtomicCounter(b *testing.B) {
|
|
c := NewAtomicCounter()
|
|
b.RunParallel(func(pb *testing.PB) {
|
|
for pb.Next() {
|
|
c.Inc()
|
|
}
|
|
})
|
|
}
|