互斥锁(Mutex)与读写锁(RWMutex)在实现线程安全方面的不同
- 互斥锁(Mutex):
- 原理:互斥锁保证在同一时刻只有一个 goroutine 能够访问共享资源。当一个 goroutine 获取了互斥锁,其他 goroutine 必须等待直到该锁被释放。
- 实现方式:Go 语言的
sync.Mutex
类型提供了 Lock
和 Unlock
方法。调用 Lock
方法获取锁,如果锁已被占用,则当前 goroutine 会被阻塞,直到锁可用。调用 Unlock
方法释放锁,允许其他等待的 goroutine 获取锁。
- 特点:简单直接,适用于读写操作都可能修改共享资源的场景。但如果读操作频繁,使用互斥锁会导致性能下降,因为读操作也会被阻塞。
- 读写锁(RWMutex):
- 原理:读写锁区分了读操作和写操作。允许多个 goroutine 同时进行读操作,因为读操作不会修改共享资源,不会引发数据竞争。但是,写操作必须是独占的,当有一个 goroutine 进行写操作时,其他任何读或写操作都必须等待。
- 实现方式:Go 语言的
sync.RWMutex
类型提供了 RLock
、RUnlock
用于读操作,Lock
、Unlock
用于写操作。多个 goroutine 可以同时调用 RLock
进行读操作,而调用 Lock
进行写操作时,会阻塞其他读和写操作。
- 特点:适用于读多写少的场景,通过允许并发读操作提高了性能。但实现相对复杂,需要正确处理读锁和写锁的获取与释放顺序,避免死锁。
实际应用场景选择
- 选择互斥锁的场景:
- 场景:当读写操作都可能修改共享资源,或者写操作较为频繁时,应该选择互斥锁。例如,一个银行账户的转账操作,涉及到余额的修改,无论是读余额还是进行转账(写操作),都需要保证线程安全,防止数据竞争。
- 示例代码:
package main
import (
"fmt"
"sync"
)
type BankAccount struct {
balance int
mutex sync.Mutex
}
func (a *BankAccount) Deposit(amount int) {
a.mutex.Lock()
a.balance += amount
a.mutex.Unlock()
}
func (a *BankAccount) Withdraw(amount int) bool {
a.mutex.Lock()
defer a.mutex.Unlock()
if a.balance >= amount {
a.balance -= amount
return true
}
return false
}
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)
}
- 选择读写锁的场景:
- 场景:当读操作远远多于写操作时,适合使用读写锁。比如一个在线文章阅读系统,大量用户会读取文章内容(读操作),而只有管理员偶尔会更新文章(写操作)。
- 示例代码:
package main
import (
"fmt"
"sync"
)
type Article struct {
content string
rwMutex sync.RWMutex
}
func (a *Article) Read() string {
a.rwMutex.RLock()
defer a.rwMutex.RUnlock()
return a.content
}
func (a *Article) Write(newContent string) {
a.rwMutex.Lock()
a.content = newContent
a.rwMutex.Unlock()
}
func main() {
article := Article{content: "Initial article content"}
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
fmt.Println("Read content:", article.Read())
wg.Done()
}()
}
go func() {
article.Write("Updated article content")
}()
wg.Wait()
fmt.Println("Final read content:", article.Read())
}