Lesson 11 / 25
Mutex and RWMutex
Protecting shared state in place.
Critical sections
sync.Mutex provides Lock and Unlock; only one goroutine at a time can hold it, so code between them (the critical section) sees consistent state. sync.RWMutex adds RLock/RUnlock, allowing many concurrent readers or one writer; it helps only when reads dominate and critical sections are not tiny, otherwise its extra bookkeeping can make it slower than a plain Mutex, so measure. Conventions: embed the mutex next to the fields it guards in a struct, keep critical sections short, use defer mu.Unlock() unless performance profiling says otherwise, never copy a struct containing a mutex after first use, and never call out to unknown code (callbacks, channel sends) while holding a lock, which invites deadlocks. Go mutexes are not reentrant: locking twice from the same goroutine deadlocks.
A concurrency-safe cache
The mutex lives next to the map it guards.
package main
import (
"fmt"
"sync"
)
type Cache struct {
mu sync.RWMutex
items map[string]string // guarded by mu
}
func NewCache() *Cache { return &Cache{items: make(map[string]string)} }
func (c *Cache) Get(k string) (string, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
v, ok := c.items[k]
return v, ok
}
func (c *Cache) Set(k, v string) {
c.mu.Lock()
defer c.mu.Unlock()
c.items[k] = v
}
func main() {
c := NewCache() // use a pointer so the mutex is never copied
var wg sync.WaitGroup
for i := range 10 {
wg.Add(1)
go func() {
defer wg.Done()
c.Set(fmt.Sprint(i), "v")
}()
}
wg.Wait()
_, ok := c.Get("3")
fmt.Println(ok) // true
}Mutex or channel?
Use a mutex when goroutines share state that stays in place (caches, counters, registries). Use channels when data or responsibility moves between goroutines (work queues, pipelines, completion signals).
Quick check: What happens if a goroutine calls Lock twice on the same sync.Mutex without unlocking?
- It panics with a reentrancy error
- The second Lock is a no-op
- The mutex counts the locks and needs two Unlocks
- It deadlocks, because Go mutexes are not reentrant
Answer
It deadlocks, because Go mutexes are not reentrant — The second Lock waits for an Unlock that the same goroutine can never reach.