读写锁是计算机程序并发控制的一种针对互斥锁优化的同步机制, 也称”共享-互斥锁”, “多读单写锁”等, 用于处理大量读, 少量写的场景.

读操作之间可并发进行, 写操作之间是互斥, 读和写又是互斥的. 这意味着多个 goroutine 可以同时读数据, 但写数据时需要获取一个独占的锁. 读写锁的常见用法是控制 goroutine 对内存中某个共享变量的访问, 这个共享变量不能被原子性地更新, 并且对此数据结构的访问大部分时间是读, 只有少量的写.

// 这是读多写少的场景
func BenchmarkCounter_Mutex(b *testing.B) {
	var counter int64

	var mu sync.Mutex

	for i := 0; i < b.N; i++ {
		b.RunParallel(func(pb *testing.PB) {
			i := 0
			for pb.Next() {
				i++

				if i%10000 == 0 {
					mu.Lock()
					counter++
					mu.Unlock()
				} else {
					mu.Lock()
					_ = counter
					mu.Unlock()
				}
			}
		})
	}
}

// BenchmarkCounter_RWMutex 读写锁测试
func BenchmarkCounter_RWMutex(b *testing.B) {
	var counter int64
	var mu sync.RWMutex

	for i := 0; i < b.N; i++ {
		b.RunParallel(func(pb *testing.PB) {
			i := 0
			for pb.Next() {
				i++

				if i%10000 == 0 {
					mu.Lock() // 使用写锁保护
					counter++
					mu.Unlock()
				} else {
					mu.RLock() // 使用读锁保护, 读取计数器的值
					_ = counter
					mu.RUnlock()
				}
			}
		})
	}
}

不同机器可能会有不同的结果, 但是可以看出读写锁对性能的提升

读写锁的使用方法

读写锁对写保护和读保护提供不同的方法, 习惯于将其称为对对写锁的操作和对读锁的操作.

与写锁相关的方法

与读锁相关的方法

读写锁 RWMutex 的零值就是无锁的状态, 一般用法是不显式地对变量进行初始化:

var mu sync.RWMutex

初始化一个包含读写锁的 struct, 也不显式地对读写锁字段进行初始化

type S struct {
	mu     sync.RWMutex
	values map[string]string
}

var s = &s{
	values: make(map[string]string),
}

读写锁的释放也是任意的, 任何 goroutine 都可以调用 RUnlock 和 Unlock, 即使它们没有持有读锁或者写锁, 跟互斥锁 Mutex 一样的, 也是不能被复制的.

读写锁的实现