面试题答案
一键面试Mutex 和 RWMutex 适用场景的不同
- Mutex(互斥锁):
- 适用场景:当需要保证共享资源在同一时刻只有一个 goroutine 可以访问时使用。它适用于读写操作都需要独占访问共享资源的场景,比如对共享资源的写入操作,或者对共享资源进行复杂修改(涉及多个步骤且需要保证原子性)的操作。因为只要有 goroutine 持有 Mutex,其他所有 goroutine 都不能访问共享资源,无论它们是读还是写。
- 示例:假设我们有一个银行账户结构体,里面有余额字段,当进行转账操作时,需要修改余额,这个操作必须是原子的,以防止并发修改导致数据不一致。
package main
import (
"fmt"
"sync"
)
type BankAccount struct {
balance int
mutex sync.Mutex
}
func (ba *BankAccount) Transfer(amount int) {
ba.mutex.Lock()
defer ba.mutex.Unlock()
ba.balance += amount
}
func main() {
var wg sync.WaitGroup
account := BankAccount{balance: 100}
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
account.Transfer(10)
}()
}
wg.Wait()
fmt.Println("Final balance:", account.balance)
}
- RWMutex(读写互斥锁):
- 适用场景:适用于读多写少的场景。它允许多个 goroutine 同时进行读操作,因为读操作不会修改共享资源,不会导致数据不一致。但是当有 goroutine 进行写操作时,其他读和写操作都必须等待,直到写操作完成。这在共享数据大部分时间被读取,偶尔被写入的场景下能显著提高性能。
- 示例:假设有一个缓存,里面存储了一些配置信息,大部分时间是被读取,偶尔会更新配置。
package main
import (
"fmt"
"sync"
)
type ConfigCache struct {
data map[string]string
rwMutex sync.RWMutex
}
func (cc *ConfigCache) Read(key string) string {
cc.rwMutex.RLock()
defer cc.rwMutex.RUnlock()
return cc.data[key]
}
func (cc *ConfigCache) Write(key, value string) {
cc.rwMutex.Lock()
defer cc.rwMutex.Unlock()
if cc.data == nil {
cc.data = make(map[string]string)
}
cc.data[key] = value
}
func main() {
var wg sync.WaitGroup
cache := ConfigCache{}
// 模拟多个读操作
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
value := cache.Read("someKey")
fmt.Println("Read value:", value)
}()
}
// 模拟一个写操作
wg.Add(1)
go func() {
defer wg.Done()
cache.Write("someKey", "someValue")
}()
wg.Wait()
}
在多 goroutine 读写共享数据场景中合理使用 RWMutex 提高性能
- 分析场景:假设我们有一个博客文章存储系统,文章内容被多个用户(多个 goroutine)读取,同时管理员偶尔会更新文章内容。这种读多写少的场景非常适合使用 RWMutex。
- 代码实现:
package main
import (
"fmt"
"sync"
)
type BlogArticle struct {
content string
rwMutex sync.RWMutex
}
func (ba *BlogArticle) ReadContent() string {
ba.rwMutex.RLock()
defer ba.rwMutex.RUnlock()
return ba.content
}
func (ba *BlogArticle) UpdateContent(newContent string) {
ba.rwMutex.Lock()
defer ba.rwMutex.Unlock()
ba.content = newContent
}
func main() {
var wg sync.WaitGroup
article := BlogArticle{content: "Initial content"}
// 模拟多个用户读取文章
for i := 0; i < 20; i++ {
wg.Add(1)
go func() {
defer wg.Done()
content := article.ReadContent()
fmt.Println("Read content:", content)
}()
}
// 模拟管理员更新文章
wg.Add(1)
go func() {
defer wg.Done()
article.UpdateContent("Updated content")
}()
wg.Wait()
}
在上述代码中,ReadContent
方法使用 RLock
允许并发读,UpdateContent
方法使用 Lock
保证写操作的独占性,从而在这种读多写少的场景下提高了性能。