首页 > 编程笔记 > Go语言笔记 阅读:6

Golang RWMutex读写锁的用法(附带实例)

互斥锁是控制多个协程访问同一个资源的常用手段,虽然它保证了数据安全,但同时也降低了性能。

互斥锁用于确保同一时刻只有一个协程访问共享资源,在写少读多的情况下,即使一段时间内没有写操作,大量的并发读操作也会在互斥锁的保护下以串行的方式进行。

而实际上,并不是所有的场景下都需要使用互斥锁来公平地对待读、写。来看个类比,我们去商场买衣服,衣服都是可以随便看的,这可以看作是“读锁”,只有看上自己喜欢的衣服我们才会去试穿,这可以看作是“写锁”。在这种场景下,“读”与“写”是区别对待的。如果只是看衣服,不管有多少人,都可以让他们看,不用排队。只有在选择试衣服时,才会把衣服取下来给他试穿,这时其他人才看不了,直到试穿完毕,归还衣服为止。

读写锁的设计策略

在设计锁的读、写策略时,借鉴了现实生活中的处理方式。当一个协程访问共享资源时,会先对其进行判断,如果它是写操作,那么就让它独占这把锁;如果不是,就让所有读操作的协程共享这把锁。这样一来,串行变成了并行,从而提高了性能。

许多编程语言都实现了类似的并发锁,即读写锁。读写锁通常都是基于互斥锁、条件变量和信号量等并发技术实现的。

如下表所示,读写锁的设计策略可以分成三类:

表:读写锁的设计策略
设计方案 设计说明
读优先级更高 这种设计可以提供高并发,但在竞争激烈的情况下可能会导致写饥饿(write-starvation)。这是因为如果有大量的读,将导致只有在所有读取都释放了锁之后写才能获取锁。
写优先级更高 这种设计主要避免了写饥饿问题。如果同一时间有一个 reader 和 writer 在等待获取锁,那么会优先给 writer,且会阻止 reader 获取锁。当然,如果 reader 已经获得了锁,那么 writer 也会等待现有 reader 释放锁,然后再获取它。
未定义优先级 这种设计相对简单,不区分 reader 和 writer 的优先级。在某些情况下,这种不指定优先级的设计更有效,因为读和写都有同样的优先权,这也解决了饥饿问题。

“写优先级更高”的RWMutex

Go 标准库中的读写锁 sync.RWMutex(简称为 RWMutex)是基于写优先级更高的方案设计的。

读写锁基于互斥锁实现,可以区别对待读和写操作:
读写锁提供的方法如下:
func (rw *RWMutex) Lock()
func (rw *RWMutex) Unlock()
 
func (rw *RWMutex) RLock()
func (rw *RWMutex) RUnlock()

Go语言RWMutex的使用示例

这里基于读写锁 RWMutex 实现了一个线程安全的计数器,示例代码如下:
type Counter struct {
    sync.RWMutex
    count uint64
}
 
// 使用读锁保护
func (c *Counter) Query() uint64 {
    c.RLock()
    defer c.RUnlock()
    return c.count
}
 
// 使用写锁保护
func (c *Counter) Increase() {
    c.Lock()
    c.count++
    c.Unlock()
}
 
func main() {
    var counter Counter
    for i := 0; i < 100; i++ {
        go func() {
             for {
                 counter.Query()
                 time.Sleep(time.Millisecond)
             }
        }()
    }
 
    for { // 1个writer
        counter.Increase() // 计数器写操作
        time.Sleep(time.Second)
    }
}
通过上述代码可以看到,方法 Increase() 是写操作,它使用方法 Lock()/Unlock() 进行加锁和释放锁操作。方法 Query() 是读操作,它使用方法 RLock()/RUnlock() 进行加锁和释放锁操作。

这里模拟的是典型的读多写少的场景,主协程每秒调用一次写操作,100 个子协程每毫秒执行一次读操作。可以看到,通过使用读写锁 RWMutex,大大提高了计数器的性能。

在上述代码中,reader 可以并发进行读操作。如果在这个场景下使用互斥锁,性能就会低很多,因为每个 reader 进行读操作时都会加锁,其他没有获取到锁的 reader 只能排队等待。

相关文章