Locking gotchas

Some care must be taken when locking and unlocking mutexes in order to avoid unexpected behavior and deadlocks in the application. Take the following snippet:

for condition {
mu.Lock()
defer mu.Unlock()
action()
}

This code seems okay at first sight, but it will inevitably block the goroutine. This is because the defer statement is not executed at the end of each loop iteration, but when the function returns. So the first attempt will lock without releasing and the second attempt will remain stuck.

A little refactor can help fix this, as shown in the following snippet:

for condition {
func() {
mu.Lock()
defer mu.Unlock()
action()
}()
}

We can use a closure to be sure that the deferred Unlock gets executed, even if action panics.

If the kind of operations that are executed on the mutex will not cause panic, it can be a good idea to ditch the defer and just use it after executing the action, as follows:

for condition {
mu.Lock()
action()
mu.Unlock()
}

defer has a cost, so it is better to avoid it when it is not necessary, such as when doing a simple variable read or assignment.