Skip to content

How to Fix Go Mutex Deadlock and Lock Contention Errors

DodaTech Updated 2026-06-24 2 min read

In this tutorial, you'll learn about How to Fix Go Mutex Deadlock and Lock Contention Errors. We cover key concepts, practical examples, and best practices.

Go mutex deadlocks occur when two goroutines each hold a lock the other needs, or when the same goroutine tries to lock a sync.Mutex it already holds. Unlike sync.RWMutex, sync.Mutex is not reentrant and will deadlock on recursive locking.

Quick Fix

Wrong

var mu sync.Mutex

func main() {
    mu.Lock()
    mu.Lock()  // deadlock: same goroutine
    mu.Unlock()
}
fatal error: all goroutines are asleep - deadlock!
var mu sync.Mutex

func main() {
    mu.Lock()
    // Do work
    mu.Unlock()
}

Fix circular lock ordering

var mu1, mu2 sync.Mutex

// Wrong: potential deadlock
go func() {
    mu1.Lock()
    mu2.Lock()
}()
go func() {
    mu2.Lock()
    mu1.Lock()
}()

// Right: consistent lock ordering
go func() {
    mu1.Lock()
    mu2.Lock()
}()
go func() {
    mu1.Lock()
    mu2.Lock()
}()

Fix missing unlock

var mu sync.Mutex
counter := 0

func increment() {
    mu.Lock()
    counter++
    // Missing Unlock() causes deadlock on next call
}

// Right: defer unlock
func increment() {
    mu.Lock()
    defer mu.Unlock()
    counter++
}

Use RWMutex for read-heavy workloads

var rwmu sync.RWMutex
data := make(map[string]int)

func read(key string) int {
    rwmu.RLock()
    defer rwmu.RUnlock()
    return data[key]
}

func write(key string, val int) {
    rwmu.Lock()
    defer rwmu.Unlock()
    data[key] = val
}

Prevention

  • Always use defer mu.Unlock() immediately after mu.Lock().
  • Use sync.RWMutex for read-heavy access patterns.
  • Establish a global lock ordering to prevent circular deadlocks.
  • Keep critical sections small to reduce contention.
  • Use go build -race to detect potential deadlocks during testing.

DodaTech Tools

Doda Browser's Go concurrency profiler visualizes lock acquisition order and detects potential deadlocks. DodaZIP archives goroutine stacks for debugging. Durga Antivirus Pro monitors lock contention in production services.

Common Mistakes with deadlock mutex

  1. Forgetting that lazy evaluation defers computation until the value is forced, causing space leaks with unevaluated thunks
  2. Using return to exit a function early instead of wrapping a pure value in the monad
  3. Mixing let bindings with <- bindings in do notation, producing type errors

These mistakes appear frequently in real-world GO code. DodaTech's contributors have identified these patterns through analysis of open-source projects and production systems.

Practice Exercise

Write a pure function that safely divides two integers using Maybe, then test it with edge cases like division by zero and negative numbers.

This exercise reinforces the concepts covered in this guide. Try implementing it before checking online solutions.

FAQ

Why does `sync.Mutex` not support recursive locking?

Recursive (reentrant) locks can hide design problems and lead to subtle bugs. Go's mutex intentionally deadlocks to force developers to restructure code. Use a different design pattern or a sync.RWMutex for nested read access.

Can I detect deadlocks automatically in Go?

Use the race detector (-race flag) during testing. For production, use pprof to analyze goroutine stacks. The runtime detects and prints deadlock information when all goroutines are blocked.

What is the difference between `sync.Mutex` and `sync.RWMutex`?

sync.Mutex allows only one goroutine to hold the lock. sync.RWMutex allows multiple readers or one writer. Use RWMutex when reads far outnumber writes.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro