Mutex and Semaphore Runtime

Updated

September 8, 2026

Mutex and Semaphore Runtime

Overview

sync.Mutex is not a pure userspace spin forever. After a short path, contending lockers park through runtime semaphores (sudog queues), integrating with the scheduler like channel waits.

Diagram: Lock fast/slow path

  Lock attempt
     │
     ├── uncontended CAS ──► enter critical section
     │
     └── contended ──► park (semacquire)
                           │
  Unlock ──► wake one waiter ──► runnable

Fast Path / Slow Path

Lock attempt
  -> atomic CAS (uncontended) success → return
  -> spin briefly (optional, implementation)
  -> enter runtime semacquire queue
  -> G parks
Unlock
  -> hand off / wake waiter
  -> woken G runnable

Implications:

  • Uncontended locks are extremely cheap
  • High contention shows up in block/mutex profiles, not only CPU
  • Holding locks across I/O serializes your service (top production mistake)

RWMutex

Multiple readers; writers exclusive. Writer starvation possible under continuous readers — measure.

Cond

sync.Cond builds on mutex + notification; easy to misuse. Prefer channels for many app-level designs; Cond appears in specialized queues.

Atomics vs Mutex

Need Prefer
Counter / flag atomic
Multi-field invariant Mutex
Coordination / ownership transfer Channel

Avoid

mu.Lock()
resp, err := http.Get(url) // BAD: lock held across I/O
mu.Unlock()

Experiment

go test -bench=. -benchmem
package lock_test

import (
    "sync"
    "sync/atomic"
    "testing"
)

func BenchmarkMutex(b *testing.B) {
    var mu sync.Mutex
    var x int
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            mu.Lock()
            x++
            mu.Unlock()
        }
    })
}

func BenchmarkAtomic(b *testing.B) {
    var x atomic.Int64
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            x.Add(1)
        }
    })
}

What to notice: Atomics win for simple counters; mutexes win for complex critical sections — profile contention, don’t guess.

Try next: Enable mutex profile on a service and find the top Unlock edge under load.