Middle
What should be remembered when working with mutexes in Go?
sobes.tech AI
Answer from AI
When working with mutexes (sync.Mutex) in Go, it is important to remember the following:
- Locking and unlocking: A mutex must be locked (
Lock()) before accessing shared data and unlocked (Unlock()) after finishing with it. Usingdefer mu.Unlock()immediately aftermu.Lock()is a good practice to prevent forgetting to unlock. - Synchronization granularity: A mutex provides exclusive access to a resource to only one goroutine at a time.
- "Locked/unlocked" state: Repeatedly calling
Lock()on an already locked mutex will block the calling goroutine until the mutex is released by another goroutine. CallingUnlock()on an unlocked mutex causes a panic during execution. - Do not copy mutexes: The
sync.Mutexstruct is not meant to be copied after first use. Passing a mutex by value to a function or copying a struct containing a mutex can lead to incorrect synchronization and panic. - Deadlocks: Occur when two or more goroutines wait for each other to release resources. Typical scenario: goroutine A locks resource R1 and waits for R2, while goroutine B locks R2 and waits for R1.
- Livelocks: Situations where goroutines constantly change their state in response to each other's actions but do not do useful work or make progress. This is less common than deadlocks.
- Starvation: When one or more goroutines cannot access a shared resource due to constant locking by other goroutines. In Go mutexes, starvation is not guaranteed.
- Using
sync.RWMutexfor read/write: If most operations are reads and writes are rare,sync.RWMutexcan be more efficient. It allows multiple goroutines to read simultaneously but blocks both reads and writes during a write. - Shortest lock durations: Try to keep locks as short as possible. The longer a mutex is locked, the more likely other goroutines will wait, reducing parallelism.
- Race condition testing: Using the
-raceflag during build and test runs (go test -race) helps detect data races often caused by improper mutex use.
Example of using defer:
import (
"sync"
)
type SafeCounter struct {
mu sync.Mutex
v map[string]int
}
// Inc safely increments the counter for a given key.
func (c *SafeCounter) Inc(key string) {
c.mu.Lock()
// Defer unlocking until the function exits.
defer c.mu.Unlock()
c.v[key]++
}
// Value returns the current value of the counter for a given key.
func (c *SafeCounter) Value(key string) int {
c.mu.Lock()
// Defer unlocking after getting the value.
defer c.mu.Unlock()
return c.v[key]
}