跳到主要内容

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 执行完毕

提示 读写锁在需要高并发读、低并发写的场景下可以显著提高性能。正确地使用读写锁,可以让程序既保证数据的安全性,又提高并发执行效率。