Initial commit: Go SRE interview exercises
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
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
// Exercise 09 — Concurrency-Safe Counters (three ways)
|
||||
//
|
||||
// ASSIGNMENT
|
||||
// ----------
|
||||
// A warm-up screener that interviewers run with -race. Implement THREE
|
||||
// versions of a keyed counter and one plain counter:
|
||||
//
|
||||
// 1. MutexCounter: map[string]int64 guarded by a sync.Mutex.
|
||||
// Inc(key), Get(key), Snapshot() (a COPY of the map — why a copy? if you
|
||||
// return the internal map, the caller can race on it).
|
||||
// 2. AtomicCounter: a single int64 total using sync/atomic only.
|
||||
// Inc(), Get(). No mutex allowed.
|
||||
// 3. ShardedCounter: N shards, each its own mutex+map, key routed by hash.
|
||||
// Same interface as MutexCounter. This is how you fix mutex contention
|
||||
// on hot maps.
|
||||
//
|
||||
// Run the tests: go test -race ./09-safe-counter/
|
||||
// Then benchmark: go test -bench=. -cpu=1,4,8 ./09-safe-counter/
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// ---- 1. Mutex version ----
|
||||
|
||||
type MutexCounter struct {
|
||||
mu sync.Mutex
|
||||
m map[string]int64
|
||||
}
|
||||
|
||||
func NewMutexCounter() *MutexCounter {
|
||||
return &MutexCounter{
|
||||
m: make(map[string]int64),
|
||||
}
|
||||
}
|
||||
func (c *MutexCounter) Inc(key string) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.m[key]++
|
||||
|
||||
}
|
||||
func (c *MutexCounter) Get(key string) int64 {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
return (c.m[key])
|
||||
}
|
||||
func (c *MutexCounter) Snapshot() map[string]int64 {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
// possibly could use maps.clone
|
||||
outmap := make(map[string]int64, len(c.m))
|
||||
for k, v := range c.m {
|
||||
outmap[k] = v
|
||||
}
|
||||
return outmap
|
||||
}
|
||||
|
||||
// ---- 2. Atomic version ----
|
||||
|
||||
type AtomicCounter struct {
|
||||
n atomic.Int64
|
||||
}
|
||||
|
||||
func NewAtomicCounter() *AtomicCounter { return &AtomicCounter{} }
|
||||
func (c *AtomicCounter) Inc() { c.n.Add(1) }
|
||||
func (c *AtomicCounter) Get() int64 { return c.n.Load() }
|
||||
|
||||
// ---- 3. Sharded version ----
|
||||
|
||||
const shards = 16
|
||||
|
||||
type shard struct {
|
||||
mu sync.Mutex
|
||||
m map[string]int64
|
||||
}
|
||||
type ShardedCounter struct {
|
||||
s [shards]shard
|
||||
// route with fnv-1a: hash(key) % shards (hash/fnv or hand-rolled)
|
||||
}
|
||||
|
||||
func NewShardedCounter() *ShardedCounter {
|
||||
c := &ShardedCounter{}
|
||||
// loop over shards and initialize each shard's map
|
||||
for i := 0; i < shards; i++ {
|
||||
c.s[i].m = make(map[string]int64)
|
||||
}
|
||||
return c
|
||||
}
|
||||
func (c *ShardedCounter) Inc(key string) {
|
||||
// find shard for the key
|
||||
hashF := fnv.New32a()
|
||||
hashF.Write([]byte(key))
|
||||
shard := hashF.Sum32() % shards
|
||||
|
||||
c.s[shard].mu.Lock()
|
||||
defer c.s[shard].mu.Unlock()
|
||||
// fmt.Println("shard", shard, "key", key, "value", c.s[shard].m[key])
|
||||
c.s[shard].m[key]++
|
||||
}
|
||||
func (c *ShardedCounter) Get(key string) int64 {
|
||||
// find shard for the key
|
||||
hashF := fnv.New32a()
|
||||
hashF.Write([]byte(key))
|
||||
shard := hashF.Sum32() % shards
|
||||
|
||||
c.s[shard].mu.Lock()
|
||||
defer c.s[shard].mu.Unlock()
|
||||
return c.s[shard].m[key]
|
||||
}
|
||||
func (c *ShardedCounter) Snapshot() map[string]int64 {
|
||||
out := make(map[string]int64)
|
||||
for i := 0; i < shards; i++ {
|
||||
c.s[i].mu.Lock()
|
||||
for k, v := range c.s[i].m {
|
||||
out[k] = v
|
||||
}
|
||||
c.s[i].mu.Unlock()
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func main() {
|
||||
c := NewMutexCounter()
|
||||
c.Inc("requests")
|
||||
c.Inc("requests")
|
||||
fmt.Println(c.Get("requests"), c.Snapshot())
|
||||
}
|
||||
Reference in New Issue
Block a user