Go 读写锁(sync.RWMutex)详解
特点
读锁定(共享锁定)
当一个 goroutine
获取读锁定时,其他 goroutine
可以同时获取读锁定。但如果有 goroutine
想获取写锁定,就会被阻塞,直到所有的读锁定被释放。
写锁定(独占锁定)
当一个 goroutine
获取写锁定时,其他任何 goroutine
(无论是读锁定还是写锁定)都会被阻塞,直到写锁定被释放。
为什么要有读写锁
读写锁允许多个读取者同时访问资源,但写入者需要独占访问。这种机制非常适合于读操作远多于写操作的情况,可以提高程序在多线程环境下的性能。
使用方法
package main
import (
"fmt"
"sync"
)
var (
rwLock sync.RWMutex
sharedData int
waitGroup sync.WaitGroup
)
func main() {
waitGroup.Add(2)
// 启动一个写操作的 goroutine
go func() {
defer waitGroup.Done()
writeData(10)
}()
// 启动一个读操作的 goroutine
go func() {
defer waitGroup.Done()
readData()
}()
waitGroup.Wait()
fmt.Println("所有 goroutine 执行完毕")
}
func readData() {
rwLock.RLock() // 获取读锁
fmt.Println("读取的数值为", sharedData)
rwLock.RUnlock() // 释放读锁
}
func writeData(value int) {
rwLock.Lock() // 获取写锁
sharedData = value
fmt.Println("写入的数值为", value)
rwLock.Unlock() // 释放写锁
}
示例解析
在上述代码中,定义了一个共享变量 sharedData
,以及一个读写锁 rwLock
。通过 waitGroup
来等待所有的 goroutine 执行完毕。
-
写操作
在
writeData
函数中,首先调用rwLock.Lock()
获取写锁,这会阻塞其他的读和写操作。写入完成后,调用rwLock.Unlock()
释放写锁。 -
读操作
在
readData
函数中,首先调用rwLock.RLock()
获取读锁。多个读操作可以同时进行,但如果有写锁存在,读操作会被阻塞。读取完成后,调用rwLock.RUnlock()
释放读锁。
注意事项
-
正确匹配锁和解锁
每次获取锁后,务必确保在函数结束前释放锁。可以使用
defer
语句来保证锁的释放,避免遗漏。 -
避免死锁
避免在获取锁后出现阻塞导致无法释放锁的情况,注意锁的获取顺序和逻辑,防止死锁发生。
-
读多写少的场景
读写锁适用于读操作远多于写操作的情况。如果写操作频繁,读写锁可能会降低性能,此时可以考虑使用互斥锁
sync.Mutex
。
示例改进
在实际应用中,可以根据需要启动多个读写 goroutine,模拟更复杂的并发场景。
func main() {
for i := 0; i < 5; i++ {
waitGroup.Add(1)
go func() {
defer waitGroup.Done()
readData()
}()
}
for i := 0; i < 2; i++ {
waitGroup.Add(1)
go func(value int) {
defer waitGroup.Done()
writeData(value)
}(i * 10)
}
waitGroup.Wait()
fmt.Println("所有 goroutine 执行完毕")
}
输出示例
写入的数值为 0
读取的数值为 0
读取的数值为 0
读取的数值为 0
写入的数值为 10
读取的数值为 10
读取的数值为 10
所有 goroutine 执行完毕
提示 读写锁在需要高并发读、低并发写的场景下可以显著提高性能。正确地使用读写锁,可以让程序既保证数据的安全性,又提高并发执行效率。