Mutex
Mutexes are synchronization primitives from the sync
package that prevent race conditions when multiple goroutines access shared data. Only one goroutine can hold the lock at a time, ensuring safe access.
Basic Usage
Wrap shared data access in a critical section:
mu.Lock()
// critical section
mu.Unlock()
Example: Protecting Shared Data
Without Mutex (race condition)
package main
import (
"fmt"
"time"
)
var counter int
func increment() {
counter++
}
func main() {
for i := 0; i < 1000; i++ {
go increment()
}
time.Sleep(time.Second)
fmt.Println("Final Counter:", counter)
}
// Final counter will likely be less than 1000 due to race conditions.
With Mutex (no race condition)
package main
import (
"fmt"
"sync"
"time"
)
var counter int
var mu sync.Mutex
func increment() {
mu.Lock()
counter++
mu.Unlock()
}
func main() {
for i := 0; i < 1000; i++ {
go increment()
}
time.Sleep(time.Second)
fmt.Println("Final Counter:", counter)
}
// Output will always be Final Counter: 1000
Best Practices
Example: Using defer
to Unlock
func increment() {
mu.Lock()
defer mu.Unlock()
counter++
}
// Ensures mutex is always unlocked, even on panic
Multiple Mutexes & Deadlocks
Always lock multiple mutexes in the same order to avoid deadlocks.
var mu1 sync.Mutex
var mu2 sync.Mutex
func routine1() {
mu1.Lock()
time.Sleep(100 * time.Millisecond)
mu2.Lock()
fmt.Println("Routine 1 finished")
mu2.Unlock()
mu1.Unlock()
}
func routine2() {
mu2.Lock()
time.Sleep(100 * time.Millisecond)
mu1.Lock()
fmt.Println("Routine 2 finished")
mu1.Unlock()
mu2.Unlock()
}
func main() {
go routine1()
go routine2()
time.Sleep(time.Second)
}
// routine1 locks mu1 then mu2, routine2 locks mu2 then mu1: causes deadlock
Mutex vs RWMutex
Example
var rw sync.RWMutex
func readData() {
rw.RLock()
fmt.Println("Reading data...")
time.Sleep(100 * time.Millisecond)
rw.RUnlock()
}
func writeData() {
rw.Lock()
fmt.Println("Writing data...")
time.Sleep(100 * time.Millisecond)
rw.Unlock()
}
When to Use Mutex
We should use a Mutex when:
- We have shared memory accessed by multiple goroutines.
- We need to protect data integrity.
- Performance is more important than waiting for safe access (mutexes introduce some blocking).
We should not use a Mutex when:
- Each goroutine works on independent data.
- Channels or message passing fits better into the design (channels often simplify concurrency).