面试题答案
一键面试主要区别
- 读写操作限制:
- sync.Mutex:互斥锁,同一时间只允许一个goroutine进行读写操作。无论是读操作还是写操作,都需要获取该锁,这保证了数据的一致性,但在高并发读场景下效率较低。
- sync.RWMutex:读写互斥锁,允许多个goroutine同时进行读操作,因为读操作不会修改数据,所以不会产生数据竞争。但是写操作时,需要独占锁,不允许其他任何读或写操作,以保证数据的一致性。
- 适用场景:
- sync.Mutex:适用于读写操作频繁且读写操作都需要保证数据一致性,不允许并发读写的场景。
- sync.RWMutex:适用于读操作远远多于写操作的场景,读操作并发执行可以提高效率,而写操作时确保独占以保证数据一致性。
适用场景举例
- 适合使用sync.Mutex的场景: 假设我们有一个银行账户结构体,每次对账户余额的修改(取款、存款操作)都需要保证数据的一致性,不允许并发操作。
package main
import (
"fmt"
"sync"
)
type BankAccount struct {
balance int
mutex sync.Mutex
}
func (b *BankAccount) Deposit(amount int) {
b.mutex.Lock()
defer b.mutex.Unlock()
b.balance += amount
}
func (b *BankAccount) Withdraw(amount int) {
b.mutex.Lock()
defer b.mutex.Unlock()
if b.balance >= amount {
b.balance -= amount
}
}
func main() {
account := BankAccount{balance: 100}
var wg sync.WaitGroup
wg.Add(2)
go func() {
account.Deposit(50)
wg.Done()
}()
go func() {
account.Withdraw(30)
wg.Done()
}()
wg.Wait()
fmt.Println("Final balance:", account.balance)
}
在这个例子中,无论是存款还是取款操作,都涉及对账户余额的修改,需要保证操作的原子性,所以使用sync.Mutex
。
- 适合使用sync.RWMutex的场景: 假设我们有一个缓存系统,缓存中的数据读取频率很高,而更新频率较低。
package main
import (
"fmt"
"sync"
)
type Cache struct {
data map[string]interface{}
rwMutex sync.RWMutex
}
func (c *Cache) Get(key string) interface{} {
c.rwMutex.RLock()
defer c.rwMutex.RUnlock()
return c.data[key]
}
func (c *Cache) Set(key string, value interface{}) {
c.rwMutex.Lock()
defer c.rwMutex.Unlock()
if c.data == nil {
c.data = make(map[string]interface{})
}
c.data[key] = value
}
func main() {
cache := Cache{}
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
key := fmt.Sprintf("key%d", id)
cache.Set(key, id)
}(i)
}
wg.Wait()
for i := 0; i < 10; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
key := fmt.Sprintf("key%d", id)
value := cache.Get(key)
fmt.Printf("Key: %s, Value: %v\n", key, value)
}(i)
}
wg.Wait()
}
在这个缓存系统中,读操作(Get
方法)可以并发执行,因为读操作不会修改数据,而写操作(Set
方法)需要独占锁以保证数据一致性,所以适合使用sync.RWMutex
。