在 Go 语言中,sync.Mutex
和 sync.RWMutex
都是用于同步的工具,用于在并发环境下保护共享资源。它们的区别主要在于功能和使用场景:sync.Mutex
是普通的互斥锁,而 sync.RWMutex
是读写锁,提供了更细粒度的控制。以下是详细对比和解释:
1. 基本概念
-
sync.Mutex
(互斥锁):- 全称是“互斥锁”(Mutual Exclusion Lock)。
- 一次只能被一个 goroutine 持有,任何其他尝试获取锁的 goroutine 都会被阻塞,直到锁被释放。
- 适用于读写都需要独占访问的场景。
-
sync.RWMutex
(读写锁):- 全称是“读写互斥锁”(Read-Write Mutex)。
- 区分了读操作和写操作:
- 读锁(RLock):允许多个 goroutine 同时获取读锁(并发读)。
- 写锁(Lock):一次只能被一个 goroutine 持有,且写锁会阻塞所有读锁和写锁(独占写)。
- 适用于读多写少的场景。
2. 方法
-
sync.Mutex
:Lock()
:加锁,锁定后其他 goroutine 无法访问资源。Unlock()
:解锁,释放锁后其他 goroutine 可以竞争获取锁。- 只有这两种状态:锁定或未锁定。
-
sync.RWMutex
:Lock()
:加写锁,独占访问,阻塞所有读锁和写锁。Unlock()
:解写锁,释放写锁。RLock()
:加读锁,允许多个 goroutine 同时持有读锁。RUnlock()
:解读锁,释放读锁。- 提供了读和写的分离控制。
3. 并发行为
-
sync.Mutex
:- 无论读还是写,所有操作都必须排队,同一时间只有一个 goroutine 可以访问资源。
- 并发性低:即使是只读操作,也会被阻塞。
-
sync.RWMutex
:- 读并发:多个 goroutine 可以同时持有读锁(
RLock
),只要没有写锁。 - 写独占:写锁(
Lock
)会阻塞所有读锁和写锁,直到写锁释放。 - 读写互斥:读锁和写锁不能同时存在。
- 并发性高:允许多个读操作并行执行。
- 读并发:多个 goroutine 可以同时持有读锁(
4. 适用场景
-
sync.Mutex
:- 适合读写操作频率差不多,或者资源访问模式简单的场景。
- 例如:需要严格顺序执行的操作(如计数器增减、单一资源修改)。
-
sync.RWMutex
:- 适合读多写少的场景。
- 例如:数据库查询(大量并发读取,偶尔更新)、缓存读取(频繁读,偶尔写)。
5. 性能对比
-
sync.Mutex
:- 实现简单,开销较低,但并发性能受限,因为所有操作都串行化。
- 在高并发读场景下效率较低。
-
sync.RWMutex
:- 实现更复杂,开销略高于
sync.Mutex
(因为需要区分读写状态)。 - 在读多写少的场景下性能更好,因为允许多个读操作并发执行。
- 实现更复杂,开销略高于
6. 示例代码
使用 sync.Mutex
package mainimport ("fmt""sync"
)func main() {var mu sync.Mutexcounter := 0var wg sync.WaitGroup// 10 个 goroutine 并发增减for i := 0; i < 10; i++ {wg.Add(1)go func() {defer wg.Add(-1)mu.Lock()counter++fmt.Println(counter)mu.Unlock()}()}wg.Wait()fmt.Println("Final counter:", counter)
}
- 结果:每次只有一个 goroutine 修改
counter
,最终结果是 10。 - 特点:所有操作串行执行。
使用 sync.RWMutex
package mainimport ("fmt""sync"
)func main() {var rwmu sync.RWMutexdata := map[string]int{"key": 0}var wg sync.WaitGroup// 5 个读 goroutinefor i := 0; i < 5; i++ {wg.Add(1)go func() {defer wg.Add(-1)rwmu.RLock() // 加读锁fmt.Println(data["key"])rwmu.RUnlock() // 解读锁}()}// 1 个写 goroutinewg.Add(1)go func() {defer wg.Add(-1)rwmu.Lock() // 加写锁data["key"] = 42rwmu.Unlock() // 解写锁}()wg.Wait()fmt.Println("Final value:", data["key"])
}
- 结果:多个读操作可以并发执行,但写操作会等待所有读锁释放,最终
data["key"]
为 42。 - 特点:读并发,写独占。
7. 关键区别总结
特性 | sync.Mutex |
sync.RWMutex |
---|---|---|
锁类型 | 互斥锁 | 读写锁 |
并发读 | 不支持(串行) | 支持(多个读锁可并存) |
并发写 | 不支持(独占) | 不支持(写锁独占) |
读写互斥 | 不区分读写,全部互斥 | 读锁和写锁互斥 |
方法 | Lock , Unlock |
Lock , Unlock , RLock , RUnlock |
复杂度 | 简单 | 稍复杂 |
性能 | 读写均衡时较优 | 读多写少时更优 |
适用场景 | 简单同步 | 高并发读、少写场景 |
8. 选择建议
- 如果你的程序读写操作频率差不多,或者逻辑简单,使用
sync.Mutex
即可。 - 如果读操作远远多于写操作,且希望提升并发性能,使用
sync.RWMutex
更合适。 - 如果需要更高级的并发控制(例如不需要手动锁),可以考虑
sync.Map
或其他并发数据结构。
如果你有具体场景想讨论(比如如何在某个代码中选择锁),告诉我,我可以帮你进一步分析!