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.