Go 语言标准库 sync 提供了 2 种锁,互斥锁(sync.Mutex)和读写锁(sync.RWMutex)。那这两种锁的区别是是什么呢?
1.1 互斥锁(sync.Mutex)
互斥即不可同时运行。即使用了互斥锁的两个代码片段互相排斥,只有其中一个代码片段执行完成后,另一个才能执行。
Go 标准库中提供了 sync.Mutex 互斥锁类型及其两个方法:
Lock 加锁
Unlock 释放锁
我们可以通过在代码前调用 Lock 方法,在代码后调用 Unlock 方法来保证一段代码的互斥执行,也可以用 defer 语句来保证互斥锁一定会被解锁。在一个 Go 协程调用 Lock 方法获得锁后,其他请求锁的协程都会阻塞在 Lock 方法,直到锁被释放。
1.2 读写锁(sync.RWMutex)
想象一下这种场景,当你在银行存钱或取钱时,对账户余额的修改是需要加锁的,因为这个时候,可能有人汇款到你的账户,如果对金额的修改不加锁,很可能导致最后的金额发生错误。读取账户余额也需要等待修改操作结束,才能读取到正确的余额。大部分情况下,读取余额的操作会更频繁,如果能保证读取余额的操作能并发执行,程序效率会得到很大地提高。
保证读操作的安全,那只要保证并发读时没有写操作在进行就行。在这种场景下我们需要一种特殊类型的锁,其允许多个只读操作并行执行,但写操作会完全互斥。
这种锁称之为 多读单写锁 (multiple readers, single writer lock),简称读写锁,读写锁分为读锁和写锁,读锁是允许同时执行的,但写锁是互斥的。一般来说,有如下几种情况:
读锁之间不互斥,没有写锁的情况下,读锁是无阻塞的,多个协程可以同时获得读锁。
写锁之间是互斥的,存在写锁,其他写锁阻塞。
写锁与读锁是互斥的,如果存在读锁,写锁阻塞,如果存在写锁,读锁阻塞。
Go 标准库中提供了 sync.RWMutex 互斥锁类型及其四个方法:
Lock 加写锁
Unlock 释放写锁
RLock 加读锁
RUnlock 释放读锁
读写锁的存在是为了解决读多写少时的性能问题,读场景较多时,读写锁可有效地减少锁阻塞的时间。
2 读写锁和互斥锁性能比较
接下来,我们测试三种情景下,互斥锁和读写锁的性能差异。
读多写少(读占 90%)
读少写多(读占 10%)
读写一致(各占 50%)
https://geektutu.com/post/hpg-mutex.html
https://segment.com/blog/allocation-efficiency-in-high-performance-go-services/