Go

Is sync.Mutex reentrant? What happens with a recursive RLock on an RWMutex?

Question 223HardGo 1.22 to 1.25

No. Go mutexes are not reentrant and are not tied to a goroutine. If the same goroutine calls Lock twice, it deadlocks. One goroutine may also Unlock a mutex that a different goroutine locked; this is legal but rarely a good idea.

A recursive RLock is the subtler trap. It works until a writer shows up:

func (s *Store) Total() int {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return s.sumLocked() + s.Count() // Count() calls RLock again
}

func (s *Store) Count() int {
	s.mu.RLock() // blocks if a writer called Lock in between
	defer s.mu.RUnlock()
	return len(s.items)
}

The sequence that deadlocks:

  1. Goroutine A holds a read lock.
  2. Goroutine B calls Lock and waits for readers to drain.
  3. A calls RLock again. That call blocks because a writer is pending.
  4. B waits on A and A waits on B.

It passes tests and then deadlocks under production load. The Go docs forbid recursive read locking explicitly.

Fix: split the code into exported methods that take the lock and unexported xxxLocked() helpers that assume the lock is already held.

More on Concurrency Patterns & sync

All 38 Concurrency Patterns & sync questions