Explain sync.Mutex "normal mode" vs "starvation mode".
Normal mode: waiters queue in FIFO order, but a woken waiter does not get the lock handed to it. It has to compete with goroutines that are arriving right now, and those newcomers are already running on a CPU (they may even be spinning), so they usually win. This keeps throughput high because the lock stays hot in the current thread's cache. The cost is that one unlucky waiter can lose again and again.
Starvation mode: if a waiter fails to get the mutex for more than 1ms, the mutex switches to starvation mode. Unlock then hands ownership directly to the waiter at the front of the queue. New arrivals do not spin and do not try to grab the lock; they join the tail of the queue.
Back to normal: the mutex returns to normal mode when a waiter that got the lock is the last one in the queue, or when it waited less than 1ms.
Gotchas:
- Starvation mode trades throughput for tail latency. A heavily contended mutex that flips into it shows up as a latency cliff.
RWMutexhas no starvation mode for readers against readers, but it does block new readers once a writer is waiting.TryLock(since Go 1.18) exists, but using it correctly is rare. It usually hides a design problem.
What the interviewer is looking for: that you know the 1ms threshold, the direct handoff, and why normal mode is unfair on purpose.
More on Concurrency Patterns & sync
- Q221When would you use sync.RWMutex over sync.Mutex, and when can RWMutex actually be slower?
- Q223Is sync.Mutex reentrant? What happens with a recursive RLock on an RWMutex?
- Q224What does this print, and why? (copying a lock)
- Q225What is the classic sync.WaitGroup bug, and what does wg.Go (Go 1.25) change?
- Q226Explain sync.Once semantics, including panics, and the OnceFunc / OnceValue / OnceValues helpers.
- Q227When would you use sync.Cond, and why must Wait always be called in a loop?