lang-wiz
a grimoire of advanced Go incantations · turn the page slowly

← back to the grimoire

Mutex

One wand. One stir at a time. Hand it on as you leave, or the brew is undone.

grimoire fragment · main.go
package main

import (
	"fmt"
	"sync"
	"time"
)

// Cauldron: a shared brew. Stirs must be tallied accurately, or the
// spell will not come out right.
type Cauldron struct {
	mu    sync.Mutex
	stirs int
}

// Stir takes the wand, adds a notch to the count, and gives the wand
// back. Only one apprentice may hold it at a time.
func (c *Cauldron) Stir(stirMs int) {
	c.mu.Lock()         // take the wand of stirring
	defer c.mu.Unlock() // pass it on as we leave
	time.Sleep(time.Duration(stirMs) * time.Millisecond)
	c.stirs++
}

func main() {
	var wg sync.WaitGroup
	c := &Cauldron{}
	for i := 0; i < 4; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			c.Stir(1500)
		}()
	}
	wg.Wait()
	fmt.Printf("brew complete; %d stirs tallied\n", 4)
}
The careful brewing
recipe ✦ Spell complete ✦ mu.Lock() — one apprentice at a time; the brew comes good
Dials & sigils

What you are witnessing

A sync.Mutex is a wand of stirring. It exists in one of two states: held, or laid down on the bench. Lock() picks it up; if another goroutine already has it, the caller waits — patiently, indefinitely — until the wand is returned. Unlock() sets it back down, and the next waiter is woken to take it. Code between the two calls is the critical section: the one stretch of work that no two apprentices may perform at the same time.

In the workshop above, the apprentices each carry a single reagent and queue up to drop it into the shared cauldron. With the mutex on, the wand is passed solemnly from hand to hand; every reagent goes in cleanly, in order, and the brew completes in a shower of stars. Turn the toggle off and watch the same apprentices lunge at once: ghost-wands proliferate, reagents collide in mid-air, and the cauldron belches purple smoke — a textbook race condition.

Why a wizard cares

Concurrent code that touches shared state without coordination doesn't merely risk the wrong answer — it risks a different wrong answer on every run, often only in production, often only under load. Go's race detector (go test -race, go run -race) can catch many such bugs, but the surest defence is to declare what you protect and protect it the same way every time. A Mutex is the smallest, sturdiest declaration there is.

The shape of the spell

  • Hold the wand only as long as you must. The critical section is the bit between Lock and Unlock. Anything not touching shared state should sit outside it — long sleeps, network calls, and other apprentices' work do not belong inside.
  • Pair Lock with defer Unlock. defer mu.Unlock() at the top of the critical section survives panics, early returns, and your own forgetfulness — exactly as defer wg.Done() does for a WaitGroup.
  • Pass by pointer. A Mutex must not be copied after first use. Embed it in a struct and pass the struct as *Cauldron, never as Cauldron.
  • Reads as well as writes. If one goroutine writes a field and another reads it, both sides need the lock. For workloads that are overwhelmingly read-only, reach for sync.RWMutex — many readers, one writer.

Pitfalls (the asterisks every wizard must read)

  • Deadlock. Two goroutines, each holding one wand and waiting for the other, will wait forever. Always acquire multiple locks in the same order, everywhere.
  • Unlocking an unlocked mutex panics. Match every Lock with exactly one Unlock, on every path.
  • Copying a Mutex after use is a silent disaster. The copy's state diverges from the original; one cauldron becomes two, and neither is what you wanted. go vet will warn you when it can spot this.
  • A mutex is not a queue. Waiters are woken in an undefined order. If you need fairness or priority, you need more than a Mutex.
  • Holding the wand across a channel send or RPC is asking for trouble. The longer the critical section, the more your other apprentices stand idle — and the more likely a slow remote turns into a deadlock involving someone else's lock too.

Reagents required: sync, already in your standard pouch. No newt eyes.