Go

What does this print, and why? (copying a lock)

Question 224MediumGo 1.22 to 1.25
type Counter struct {
	mu sync.Mutex
	n  int
}

func (c Counter) Inc() { // value receiver
	c.mu.Lock()
	defer c.mu.Unlock()
	c.n++
}

func main() {
	var c Counter
	var wg sync.WaitGroup
	for range 100 {
		wg.Go(c.Inc)
	}
	wg.Wait()
	fmt.Println(c.n)
}

It prints 0. The value receiver copies the whole struct, including the mutex, so every call locks its own private mutex and increments its own copy of n.

Two more ways copying bites:

  • Copying a mutex while it is locked produces a copy that is also locked. Any later Lock on the copy deadlocks.
  • Locks get copied implicitly by for _, v := range sliceOfStructs, by passing structs by value, and by returning them from functions.

Fix: use a pointer receiver func (c *Counter) Inc() and never copy a value after first use. This applies to Mutex, RWMutex, WaitGroup, Once, Cond, Pool, sync.Map and the typed atomics.

go vet's copylocks check catches most of these. Types that must not be copied embed a zero-size noCopy marker, and you can add the same marker to your own types.

More on Concurrency Patterns & sync

All 38 Concurrency Patterns & sync questions