序言
本文介绍了golang中RWMutex的使用技巧、使用场景以及实现原理,简单阐述了为什么有了Mutex互斥锁之后,golang还给我们提供了对Mutex互斥锁进一步封装的RWMutex读写锁。
1. RWMutex是什么
上一期中,我们讲解了golang中Mutex互斥锁,这期就来谈谈goalng中的另一个锁,RWMutex读写锁,他是对Mutex互斥锁的进一步封装,通过区分对临界资源访问读或写的不同操作,实现了RWMutex中的不同加锁解锁逻辑,以提高对临界资源访问性能。读取临界资源加读锁可以并发读,而写则会加写锁,不允许并发写和读,提高了在读并发量大,而写并发量少应用场景下的性能,在这样的应用场景下,相对于使用Mutex互斥锁,使用RWMutex,程序性能将会有大幅提升。
2. 为什么要有RWMutex,他有怎样的应用场景
上一期我们谈到的Mutex是golang中最基本的一个同步原语,它的加锁解锁都是相对于某一临界资源而言,没有对临界资源的操作进行区分,然而通常在访问某一些临界资源时,读取数据和写入数据的概率并不相同,某些数据大部分时间用来读取分析,修改写入概率小,这时候如果我们还是用Mutex互斥锁来保护临界资源的话就会导致我们读取数据由于Mutex互斥锁的存在而变为串行执行,导致程序运行效率低下,然而我们读取业务中,并不需要串行执行,如果数据没有发生改变的话,并发读取数据也是安全的,只有当更改操作时,才需要加锁,这时候区分读写操作来加锁就变得尤为重要,于是RWMutex读写锁就派上了用场。
RWMutex读写锁中通过区分读锁和写锁,来实现了读取数据并发读,写入数据互斥写的功能,在实现了临界资源并发安全的前提下,也提高了并发读取数据的性能。
3. RWMutex的基本使用与易错场景
3.1 RWMutex的基本使用
接下来通过简单介绍RWMutex的读锁加锁解锁,写锁加锁解锁的方法来学习如何使用RWMutex提高并发读的性能。
如下实现为一个读写分离并且并发安全的计数器。
func main() {
var counter Counter
for i := 0; i < 10; i++ { // 10个reader
go func() {
for {
fmt.Println("--读取到的数据:--", counter.Count(), "--读取到的数据:--") // 计数器读操作
time.Sleep(time.Millisecond)
}
}()
}
for { // 一个writer
counter.Incr() // 计数器写操作
time.Sleep(time.Millisecond * 10)
}
}
// Counter 一个线程安全的计数器
type Counter struct {
mu sync.RWMutex
count uint64
}
// Incr 使用写锁保护
func (c *Counter) Incr() {
c.mu.Lock()
c.count++
c.mu.Unlock()
}
// Count 使用读锁保护
func (c *Counter) Count() uint64 {
c.mu.RLock()
defer c.mu.RUnlock()
return c.count
}
总的来说,读锁和读锁之间不互斥,而读锁写锁、写锁写锁之间互斥,简单来说就是当临界资源要写数据时,只能一个协程来写,就是占用写锁的那个协程,不允许任何协程读,当临界资源有写锁,允许多个协程同步读,但是不允许任何一个协程写,多个协程之间不再是串行读,实现了读的并发安全和写的并发安全的同时也提高了读的运行效率。
3.2 RWMutex常见易错场景
3.2.1 不可复制
3.2.2 不可重入
3.2.3 释放未加锁的RWMutex
4. RWMutex的实现原理
4.1 五个字段
type RWMutex struct {
w Mutex // 互斥锁,解决写者写操作之间的数据竞争
writerSem uint32 // 写者信号量
readerSem uint32 // 读者信号量
readerCount atomic.Int32 // 记录读者的数量
readerWait atomic.Int32 // 记录写者请求锁需要等待的读者数量
}
4.2 六大方法
4.2.1 读锁:RLock/RUnlock读加锁解锁,TryRLock获取读锁
4.2.1.1 RLock加读锁
读操作时调用的加锁方法,如果锁已经被写资源占有,RLock就会一直阻塞,直到能获取到锁,否则直接返回,
4.2.1.2 RUnlock解读锁
读操作释放锁的方法
4.2.1.3 TryRLock获取读锁
读者尝试获取读锁的方法,返回值为bool类型,返回true标识获取到了读锁
4.2.2 写锁:Lock/Unlock写加锁解锁,TryLock获取写锁
4.2.2.1 Lock加写锁
写操作加锁的方法,如果锁已经被读资源或者写资源占有,写锁都会阻塞等待,直到能获取到写锁
4.2.2.2 Unlock解写锁
释放写锁
4.2.2.3 TryLock获取写锁
写者尝试获取写锁的方法,返回值为bool类型,返回true标识获取到了写锁
//还没写完,慢慢更