Skip to content

读写锁(Read-Write Lock) ​

读写锁是一种特殊的锁,它允许多个读操作并发执行,但写操作是独占的。也就是说,在同一时间,可以有多个线程同时读取共享资源,但只有一个线程可以写入共享资源,并且在写入时不允许其他线程读取。

读写锁特别适用于读多写少的场景,因为它可以提高并发性和性能。

Go中的读写锁 ​

在Go语言中,读写锁由标准库sync包提供。常用的类型是sync.RWMutex,它有以下方法:

  • RLock(): 获取读锁。如果写锁已被持有,调用此方法的Goroutine会阻塞,直到写锁被释放。
  • RUnlock(): 释放读锁。
  • Lock(): 获取写锁。如果读锁或写锁已被持有,调用此方法的Goroutine会阻塞,直到所有锁都被释放。
  • Unlock(): 释放写锁。

读写锁的例子 ​

下面是一个简单的例子,展示如何在Go中使用读写锁来保护共享资源。

go
package main

import (
	"fmt"
	"sync"
	"time"
)

var (
	counter int
	rwMutex sync.RWMutex
)

func readCounter(id int, wg *sync.WaitGroup) {
	defer wg.Done()
	rwMutex.RLock()   // 获取读锁
	defer rwMutex.RUnlock() // 在函数退出时释放读锁
	fmt.Printf("Goroutine %d reading counter: %d\n", id, counter)
	time.Sleep(100 * time.Millisecond) // 模拟读取操作
}

func writeCounter(id int, wg *sync.WaitGroup) {
	defer wg.Done()
	rwMutex.Lock()   // 获取写锁
	defer rwMutex.Unlock() // 在函数退出时释放写锁
	counter++
	fmt.Printf("Goroutine %d writing counter: %d\n", id, counter)
	time.Sleep(100 * time.Millisecond) // 模拟写入操作
}

func main() {
	var wg sync.WaitGroup

	// 启动5个读操作
	for i := 1; i <= 5; i++ {
		wg.Add(1)
		go readCounter(i, &wg)
	}

	// 启动2个写操作
	for i := 1; i <= 2; i++ {
		wg.Add(1)
		go writeCounter(i, &wg)
	}

	wg.Wait()
	fmt.Println("Final Counter:", counter)
}

代码解释 ​

  1. 定义读写锁和共享变量:

    go
    var (
        counter int
        rwMutex sync.RWMutex
    )
  2. 使用读写锁保护读操作:

    go
    func readCounter(id int, wg *sync.WaitGroup) {
        defer wg.Done()
        rwMutex.RLock()   // 获取读锁
        defer rwMutex.RUnlock() // 在函数退出时释放读锁
        fmt.Printf("Goroutine %d reading counter: %d\n", id, counter)
        time.Sleep(100 * time.Millisecond) // 模拟读取操作
    }
  3. 使用读写锁保护写操作:

    go
    func writeCounter(id int, wg *sync.WaitGroup) {
        defer wg.Done()
        rwMutex.Lock()   // 获取写锁
        defer rwMutex.Unlock() // 在函数退出时释放写锁
        counter++
        fmt.Printf("Goroutine %d writing counter: %d\n", id, counter)
        time.Sleep(100 * time.Millisecond) // 模拟写入操作
    }
  4. 主函数:

    go
    func main() {
        var wg sync.WaitGroup
    
        // 启动5个读操作
        for i := 1; i <= 5; i++ {
            wg.Add(1)
            go readCounter(i, &wg)
        }
    
        // 启动2个写操作
        for i := 1; i <= 2; i++ {
            wg.Add(1)
            go writeCounter(i, &wg)
        }
    
        wg.Wait()
        fmt.Println("Final Counter:", counter)
    }

在主函数中,启动了5个读Goroutine和2个写Goroutine。读Goroutine获取读锁,可以并发读取counter的值。而写Goroutine获取写锁,确保在写入时没有其他Goroutine读取或写入counter。在所有Goroutine执行完毕后,打印出最终的counter值。

小结 ​

通过使用读写锁,可以提高读多写少场景下的并发性能。在Go语言中,使用sync.RWMutex提供的RLock、RUnlock、Lock和Unlock方法,可以方便地实现对读写操作的保护,确保数据的一致性和正确性。

Hello Golang