A mutex (short for mutual exclusion) is a synchronization primitive used to ensure that only one goroutine can access a critical section of code at a time. This prevents data races and ensures the integrity of shared resources.
-
sync.Mutex
- This is the basic mutex type. It supports exclusive locking (i.e., only one goroutine can lock it at a time).
-
sync.RWMutex (Read/Write Mutex)
- This is a more advanced form of a mutex, which allows multiple readers but only one writer. It is useful when a resource is often read but rarely modified.
A Mutex
from the sync
package is typically used to protect shared data by preventing multiple goroutines from accessing the same data at the same time.
import "sync"
var mu sync.Mutex
You can lock and unlock a mutex with the Lock()
and Unlock()
methods.
mu.Lock() // Acquire the lock
// Critical section
mu.Unlock() // Release the lock
package main
import (
"fmt"
"sync"
)
var mu sync.Mutex
var counter int
func increment() {
mu.Lock() // Acquire the lock
counter++ // Increment the counter
mu.Unlock() // Release the lock
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
increment()
}()
}
wg.Wait()
fmt.Println("Final counter:", counter) // Output: Final counter: 10
}
- Always unlock a locked mutex: If you forget to unlock a mutex, it will block other goroutines indefinitely, causing a deadlock.
- Defer Unlocking: You can use
defer
to ensure the mutex is unlocked even if an error occurs in the critical section.
sync.RWMutex
is a more specialized form of a mutex, which allows multiple goroutines to acquire the lock for reading concurrently, but only one goroutine can acquire the lock for writing. This is useful when you have more read operations than write operations, and you want to maximize concurrency.
import "sync"
var rwMu sync.RWMutex
- RLock(): Acquires the lock for reading (shared access).
- Lock(): Acquires the lock for writing (exclusive access).
- Unlock(): Releases the lock.
rwMu.RLock() // Acquire a read lock (shared lock)
rwMu.RUnlock() // Release the read lock
rwMu.Lock() // Acquire a write lock (exclusive lock)
rwMu.Unlock() // Release the write lock
package main
import (
"fmt"
"sync"
)
var rwMu sync.RWMutex
var counter int
// Read function
func readCounter() int {
rwMu.RLock() // Acquire a read lock
defer rwMu.RUnlock() // Ensure the lock is released
return counter
}
// Write function
func incrementCounter() {
rwMu.Lock() // Acquire a write lock
defer rwMu.Unlock() // Ensure the lock is released
counter++
}
func main() {
var wg sync.WaitGroup
// Start 5 goroutines to read the counter
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println("Counter value:", readCounter())
}()
}
// Start 1 goroutine to increment the counter
wg.Add(1)
go func() {
defer wg.Done()
incrementCounter()
fmt.Println("Counter incremented.")
}()
wg.Wait() // Wait for all goroutines to finish
}
- Read-Heavy Workloads: Use
RWMutex
when you have many goroutines that need to read data but only occasionally need to write to it. This allows you to maximize concurrency for read operations. - Write-Heavy Workloads: If the resource is frequently modified, a
sync.Mutex
might be a better choice, asRWMutex
introduces overhead for managing read and write locks.
- Read Lock (RLock): Multiple goroutines can hold the read lock simultaneously, but no goroutine can write when a read lock is held.
- Write Lock (Lock): Only one goroutine can hold the write lock at a time, and it prevents any other goroutine from reading or writing.
- Use
sync.Mutex
when you have exclusive access to shared resources. It’s simpler and works well for small code sections with fewer goroutines. - Use
sync.RWMutex
when you have frequent read operations and occasional writes. It can improve performance by allowing multiple readers to access shared data concurrently while ensuring exclusive access for writers. - Avoid Deadlocks: Always be cautious when locking multiple mutexes. Lock them in the same order to avoid circular waiting, which could lead to a deadlock.