sync.RWMutex和sync.Mutex有什么区别

在 Go 语言中,sync.Mutexsync.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)会阻塞所有读锁和写锁,直到写锁释放。
    • 读写互斥:读锁和写锁不能同时存在。
    • 并发性高:允许多个读操作并行执行。

4. 适用场景

  • sync.Mutex

    • 适合读写操作频率差不多,或者资源访问模式简单的场景。
    • 例如:需要严格顺序执行的操作(如计数器增减、单一资源修改)。
  • sync.RWMutex

    • 适合读多写少的场景。
    • 例如:数据库查询(大量并发读取,偶尔更新)、缓存读取(频繁读,偶尔写)。

5. 性能对比

  • sync.Mutex

    • 实现简单,开销较低,但并发性能受限,因为所有操作都串行化。
    • 在高并发读场景下效率较低。
  • sync.RWMutex

    • 实现更复杂,开销略高于 sync.Mutex(因为需要区分读写状态)。
    • 在读多写少的场景下性能更好,因为允许多个读操作并发执行。

6. 示例代码

使用 sync.Mutex

package main

import (
	"fmt"
	"sync"
)

func main() {
	var mu sync.Mutex
	counter := 0
	var 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 main

import (
	"fmt"
	"sync"
)

func main() {
	var rwmu sync.RWMutex
	data := map[string]int{"key": 0}
	var wg sync.WaitGroup

	// 5 个读 goroutine
	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func() {
			defer wg.Add(-1)
			rwmu.RLock()           // 加读锁
			fmt.Println(data["key"])
			rwmu.RUnlock()         // 解读锁
		}()
	}

	// 1 个写 goroutine
	wg.Add(1)
	go func() {
		defer wg.Add(-1)
		rwmu.Lock()            // 加写锁
		data["key"] = 42
		rwmu.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 或其他并发数据结构。

如果你有具体场景想讨论(比如如何在某个代码中选择锁),告诉我,我可以帮你进一步分析!

posted @ 2025-03-03 13:55  仁义礼智信的  阅读(99)  评论(0)    收藏  举报