Essential Go  Suggest an edit

Mutex

In Go goroutines share memory.

It’s good for performance but modifying the same memory from multiple goroutines is not safe. It can lead to data races and crashes.

One way to avoid that is by using channels to transfer exclusive ownership of data.

This is Go’s motto: do not communicate by sharing memory; instead, share memory by communicating.

Another way to ensure exclusive access to data by goroutines is to use mutexes.

Here’s a basic pattern that uses a map for a cache and ensures exclusive access with mutex locking:

var cache map[int]int
var mu sync.Mutex

func expensiveOperation(n int) int {
	// in real code this operation would be very expensive
	return n * n
}

func getCached(n int) int {
	mu.Lock()
	v, isCached := cache[n]
	mu.Unlock()
	if isCached {
		return v
	}

	v = expensiveOperation(n)

	mu.Lock()
	cache[n] = v
	mu.Unlock()
	return v
}

func accessCache() {
	total := 0
	for i := 0; i < 5; i++ {
		n := getCached(i)
		total += n
	}
	fmt.Printf("total: %d\n", total)
}

cache = make(map[int]int)
go accessCache()
accessCache()
total: 30
total: 30

Zero value of sync.Mutex is a valid mutex so you don’t need to explicitly initialize it.

For performance we want to minimize the time spent holding a lock.

Function getCached would be simpler if we kept the mutex locked for the duration of the function but we don’t want to keep cache locked when we’re executing expensiveOperation().

Unlike many other languages, Go mutexes are non-recursive.

If the same goroutines tries to Lock() a mutex twice, the second Lock() will block forever.

  ↑ ↓ to navigate     ↵ to select     Esc to close