Questions
What is the difference between `sync.Mutex` and `sync.RWMutex` in Go?
The Scenario
You are a backend engineer at a social media company. You are building a new service that has a shared resource that is read by many goroutines and written to by a few goroutines.
You need to find a way to protect the shared resource from concurrent access.
The Challenge
Explain the difference between sync.Mutex and sync.RWMutex in Go. What are the pros and cons of each approach, and which one would you choose for this use case?
A junior engineer might just use a `sync.Mutex` for all use cases. They might not be aware of the `sync.RWMutex` or the performance benefits of using a read-write mutex.
A senior engineer would know that a `sync.RWMutex` is the perfect tool for this job. They would be able to explain the difference between a `sync.Mutex` and a `sync.RWMutex`, and they would be able to explain the performance benefits of using a read-write mutex.
Step 1: Understand the Key Differences
| Feature | sync.Mutex | sync.RWMutex |
|---|---|---|
| Locking | Provides an exclusive lock. Only one goroutine can hold the lock at a time. | Provides a read-write lock. Multiple goroutines can hold a read lock at the same time, but only one goroutine can hold a write lock at a time. |
| Use Cases | When you need to protect a shared resource from concurrent access, and the resource is written to frequently. | When you have a shared resource that is read frequently and written to infrequently. |
Step 2: Choose the Right Tool for the Job
For our use case, a sync.RWMutex is the best choice. It will allow multiple goroutines to read the shared resource at the same time, which will improve performance.
Step 3: Code Examples
Here are some code examples that show the difference between the two approaches:
sync.Mutex:
package main
import (
"fmt"
"sync"
)
var (
counter int
mutex sync.Mutex
)
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
mutex.Lock()
counter++
mutex.Unlock()
}()
}
wg.Wait()
fmt.Println(counter)
}sync.RWMutex:
package main
import (
"fmt"
"sync"
)
var (
counter int
mutex sync.RWMutex
)
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
mutex.RLock()
fmt.Println(counter)
mutex.RUnlock()
}()
}
wg.Wait()
} Practice Question
You have a shared resource that is written to frequently and read infrequently. Which of the following would be the most appropriate?