Mutex
sync.Mutex provides mutual exclusion — only one goroutine holds the lock at a time.
Introduction
sync.Mutex provides mutual exclusion — only one goroutine holds the lock at a time. RWMutex allows multiple readers or one writer. Use mutexes when goroutines share mutable state: counters, caches, connection pools metadata.
The race detector (go test -race) finds unsynchronized access — run it in CI. Prefer channels for ownership transfer; use mutexes for protecting shared data structures. sync/atomic offers lock-free counters for simple cases.
Deadlocks from lock ordering and forgotten Unlock are common production bugs — defer Unlock immediately after Lock.
The story
An API gateway maintains an in-memory rate-limit map shared across thousands of goroutines handling concurrent requests. A sync.Mutex protects map writes while sync.RWMutex allows parallel reads during traffic peaks — the same pattern in Cloudflare's edge rate limiters where read-heavy workloads dominate.
Data races corrupt maps silently; run go test -race in CI to catch missing locks before they become production incidents.
Understanding the topic
Key concepts
- mu.Lock() / mu.Unlock() — exclusive access.
- RWMutex: RLock/RUnlock for readers; Lock/Unlock for writers.
- defer mu.Unlock() immediately after Lock — panic-safe.
- sync/atomic for simple counters without full mutex.
- Don't copy Mutex — contains internal state; use pointer.
- Race detector flags unsynchronized concurrent access.
Step-by-step explanation
- Goroutine Lock blocks until mutex available.
- Unlock releases; next waiting goroutine proceeds.
- RLock allows concurrent readers; Lock blocks all.
- Lock while holding Lock same mutex — deadlock.
- Zero value Mutex ready to use — no initialization needed.
- atomic.AddInt64 for hot-path counters.
Practical code example
Thread-safe cache with RWMutex:
package mainimport ("fmt""sync")type SafeCache struct {mu sync.RWMutexitems map[string]string}func NewSafeCache() *SafeCache {return &SafeCache{items: make(map[string]string)}}func (c *SafeCache) Get(key string) (string, bool) {c.mu.RLock()defer c.mu.RUnlock()val, ok := c.items[key]return val, ok}func (c *SafeCache) Set(key, val string) {c.mu.Lock()defer c.mu.Unlock()c.items[key] = val}func main() {cache := NewSafeCache()var wg sync.WaitGroupfor i := 0; i < 10; i++ {wg.Add(1)go func(n int) {defer wg.Done()key := fmt.Sprintf("k%d", n%3)cache.Set(key, fmt.Sprintf("v%d", n))cache.Get(key)}(i)}wg.Wait()fmt.Println("cache safe after concurrent access")}
Line-by-line code explanation
var mu sync.Mutexdeclares a mutual exclusion lock.mu.Lock()acquires the lock — blocks if another goroutine holds it.defer mu.Unlock()releases the lock when the function returns, even on panic.limits[key]++safe map mutation only while the mutex is held.sync.RWMutex—RLock/RUnlockfor concurrent readers,Lockfor exclusive writers.sync.Map— specialized concurrent map for read-heavy caches (use when profiled).go test -race ./...detects unsynchronized access in CI pipelines.hold locks briefly— never perform I/O or network calls while holding a mutex.
Key takeaway: RLock for read-heavy Get; Lock for write Set. defer Unlock prevents leak on panic. Never copy SafeCache struct containing Mutex.
Real-world use
Where you'll use this in production
- In-memory rate limiter and session store.
- Metrics counter aggregation from many goroutines.
- Lazy initialization of expensive singleton resources.
- Protecting map writes in concurrent cache.
Best practices
- defer mu.Unlock() immediately after Lock.
- Hold lock for minimal critical section — no I/O inside lock.
- Consistent lock ordering across goroutines prevents deadlock.
- Use RWMutex when reads dominate writes.
- Run go test -race in CI on every PR.
- Consider sync.Map for read-heavy cache instead of mutex+map.
Common mistakes
- Forgotten Unlock — deadlock.
- Locking during network call — throughput collapse.
- Copying struct with embedded Mutex.
- RWMutex: writer starvation under heavy read load — monitor.
- Using mutex when channel ownership transfer is clearer.
Advanced interview questions
Interview Prep
Practice concise answers, then expand each card for the explanation.
1BeginnerQuestionMutex vs channel?+
Answer
2BeginnerQuestionRWMutex when?+
Answer
3IntermediateQuestionWhy defer Unlock?+
Answer
4IntermediateQuestionDetect data race?+
Answer
5AdvancedQuestionDesign concurrent cache with TTL eviction.+
Answer
Summary
Mutex serializes access to shared mutable state. RWMutex optimizes read-heavy workloads. Always defer Unlock; keep critical sections small. Run race detector in CI — catches unsynchronized access. Next lesson: context — cancellation and deadlines.