Fundamentals 17 min read

Understanding Locks and Mutex Implementation in Go

This article explains the concept of locks, why they are needed in concurrent programming, and provides an in‑depth look at Go's synchronization primitives—including CAS, atomic operations, spinlocks, semaphores, and the evolution of the sync.Mutex implementation with code examples and performance considerations.

Xueersi Online School Tech Team
Xueersi Online School Tech Team
Xueersi Online School Tech Team
Understanding Locks and Mutex Implementation in Go

What Is a Lock

A lock is a resource maintained by the operating system for synchronization; a mutex is a mutually exclusive resource that only one thread can hold at a time.

Why Use Locks

Locks ensure data consistency and safety in concurrent programming.

Locks in Go

Go provides synchronization mechanisms in the sync package, such as Mutex , WaitGroup , and channels, built on low‑level primitives like CAS, atomic operations, spinlocks, and semaphores.

1. CAS and Atomic Operations

CAS (Compare And Swap) and atomic operations are the foundation of other synchronization mechanisms.

Atomic operation: an indivisible operation that cannot be interrupted.

CAS: a non‑blocking atomic operation that may need to retry in high‑contention scenarios.

2. Spinlock

A spinlock repeatedly checks the lock state while actively waiting, avoiding thread sleep; Go uses spinlocks internally to implement other lock types.

3. Semaphore

Semaphores implement sleep and wake‑up for goroutines using P (acquire) and V (release) operations.

P(S): allocate a resource, decrement the count, and block if the count becomes negative. V(S): release a resource, increment the count, and wake a blocked goroutine if needed.

Mutex Usage Example

package main

import (
    "fmt"
    "sync"
)

var num int
var mtx sync.Mutex
var wg sync.WaitGroup

func add() {
    mtx.Lock() // mutex does not need explicit instantiation
    defer mtx.Unlock()
    defer wg.Done()
    num += 1
}

func main() {
    // Launch 100 goroutines, each incrementing the same counter.
    // Without the lock, the final value may not be 100.
    for i := 0; i < 100; i++ {
        wg.Add(1)
        go add()
    }
    wg.Wait()
    fmt.Println("num:", num)
}

Why Mutexes Are Necessary

Locks suspend and resume threads under high contention, whereas atomic operations keep the CPU busy; atomic ops are thread‑level only and do not support goroutines, so mutexes are preferred for many concurrent scenarios.

Evolution of Mutex

The modern sync.Mutex is more complex than early versions. Initially, a contended lock caused the goroutine to sleep; later versions introduced spin‑waiting and a starvation‑free mode to improve latency.

1. Basic Mutex Structure

type Mutex struct {
    state int32
    sema  uint32
}

const (
    mutexLocked = 1 << iota
    mutexWoken
    mutexWaiterShift // number of waiters = state >> mutexWaiterShift
)

The state field encodes lock status, wake‑up flag, and waiter count using bit masks.

Lock Algorithm

No contention: acquire lock via CAS.

Contention: call runtime_Semacquire to put the goroutine to sleep.

func (m *Mutex) Lock() {
    // Fast path: try CAS to set locked state.
    if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
        return
    }
    awoke := false
    for {
        old := m.state
        new := old | mutexLocked
        if old&mutexLocked != 0 {
            new = old + 1<

Unlock Algorithm

Release the lock via CAS.

Wake a sleeping goroutine, decreasing the waiter count.

func (m *Mutex) Unlock() {
    if raceenabled {
        raceRelease(unsafe.Pointer(m))
    }
    new := atomic.AddInt32(&m.state, -mutexLocked)
    if (new+mutexLocked)&mutexLocked == 0 {
        panic("sync: unlock of unlocked mutex")
    }
    for {
        if new>>mutexWaiterShift == 0 || new&(mutexLocked|mutexWoken) != 0 {
            return // no waiter or lock already handed off
        }
        // Wake one waiter.
        new = (new - 1<

Fair (Starvation‑Free) Mutex

Go's mutex has two modes: normal and starvation. If a goroutine waits longer than 1 ms, the mutex switches to starvation mode, handing the lock directly to the first waiter, reducing long‑tail latency.

References

《go sync.Mutex 设计思想与演化过程 (一)博客园-暮夏》

《GO: sync.Mutex 的实现与演进 简书-董泽润》

《golang之sync.Mutex互斥锁源码分析 简书-freelang》

《Golang同步机制的实现 go语言中文网-无心之祸》

《Golang 并发编程与同步原语 segmentfault-draveness》

《锁的本质 csdn-DIY-GEEKER》

backendconcurrencygolangGosynchronizationmutexlock
Xueersi Online School Tech Team
Written by

Xueersi Online School Tech Team

The Xueersi Online School Tech Team, dedicated to innovating and promoting internet education technology.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.