HowtoGo
Home / Concurrency / Mutexes
Concurrency

Mutexes

A sync.Mutex lets only one goroutine run a section of code at a time. Lock and Unlock mark where that section starts and ends.

Lock blocks until no other goroutine holds the mutex, then claims it. Unlock releases it. Only the code between the two runs one goroutine at a time.

var mu sync.Mutex
counter := 0

mu.Lock()
counter++
mu.Unlock()

fmt.Println(counter) // 1

defer mu.Unlock() placed right after Lock guarantees the mutex releases even if the goroutine returns early or panics. Without it, all 100 goroutines here would race on counter.

var mu sync.Mutex
var wg sync.WaitGroup
counter := 0

for i := 0; i < 100; i++ {
    wg.Add(1)
    go func() {
        defer wg.Done()
        mu.Lock()
        defer mu.Unlock()
        counter++
    }()
}
wg.Wait()
fmt.Println(counter) // 100
FunctionDescription
Lock()
Blocks until the mutex is free, then claims it.
Unlock()
Releases a locked mutex. Panics if it isn't locked.
TryLock() bool
Claims the mutex only if it's free right now; never blocks.
RLock()
Claims a read lock. Any number of readers can hold it at once (RWMutex only).
RUnlock()
Releases a read lock (RWMutex only).
TryRLock() bool
Claims a read lock only if no writer holds it; never blocks (RWMutex only).

RWMutex, and a copying gotcha

RWMutex lets any number of readers hold RLock at once, but Lock still excludes everyone. It pays off only when reads heavily outnumber writes.

type Cache struct {
    mu   sync.RWMutex
    data map[string]int
}

func (c *Cache) Get(key string) int {
    c.mu.RLock()
    defer c.mu.RUnlock()
    return c.data[key]
}

func (c *Cache) Set(key string, v int) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.data[key] = v
}

A sync.Mutex must never be copied after first use. Passing c Counter by value gives inc its own copy of mu, so the lock protects nothing. go vet flags this; the fix is a pointer receiver, func inc(c *Counter).

type Counter struct {
    mu sync.Mutex
    n  int
}

func inc(c Counter) { // BUG: c is a copy
    c.mu.Lock()
    c.n++
    c.mu.Unlock()
}

100 goroutines increment the same counter, each holding the mutex for its single counter++. The result is always 100, never less.

Terminal
$ go run mutexes.go
100