# Mutex and RWMutex — Go Concurrency Patterns

Source: https://www.geekswithgeeks.com/en/go-concurrency/gc-mutex

> 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.

```go
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).

**Quiz:** 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
- [x] 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.
