前言
距离第一篇文章已经经过了4年,最近几天突然想起之前面试时被问过的一个问题,由于个人小菜鸡一枚,当时未能及时解答,如今想想感觉这个问题还是蛮有意思的,想通过人生的第二篇技术blog来记录一下.
正题
关于读写锁的相关内容,如读者对此还处于陌生状态,可参考维基百科 [读写锁] (建议尽量阅读英语文本, 英语不好的最好中英参照着看)
实现方式简要概括主要通过一个互斥锁来全局控制是否锁住当前代码块,一个条件变量(即go标准库中的sync.Cond)来充当读锁的计数器.
具体逻辑作者也是参考维基百科的伪代码来实现的:
这里提一嘴,此处实现方式是基于读优先级来进行实现,因此可能会产生写锁迟迟被读锁卡住的情况,而 go 内部实现是以写优先级的方式实现,而写优先级实现的缺点是逻辑更为复杂,因此性能也不如前者,这个维基百科上也有提及。这个话题暂时就说到这里。
实现
type RWLocker interface {
RLock()
RUnlock()
Lock()
Unlock()
}
type RWLockerImpl struct {
mu sync.Mutex
reader *sync.Cond
readerCount int
}
func (l *RWLockerImpl) Lock() {
l.reader.L.Lock()
for l.readerCount > 0 {
l.reader.Wait() // 如果读锁存在则阻塞, 待读锁全部释放
}
l.reader.L.Unlock()
l.mu.Lock()
}
func (l *RWLockerImpl) Unlock() {
l.mu.Unlock()
}
func (l *RWLockerImpl) RLock() {
l.reader.L.Lock()
l.readerCount++
if l.readerCount == 1 { // 避免读锁之间阻塞
l.mu.Lock()
}
l.reader.L.Unlock()
}
func (l *RWLockerImpl) RUnlock() {
l.reader.L.Lock()
if l.readerCount == 0 {
l.reader.L.Unlock()
return
}
l.readerCount--
if l.readerCount == 0 {
l.mu.Unlock()
}
l.reader.L.Unlock()
l.reader.Broadcast() // 通知已经有读锁释放
}
func NewLocker() RWLocker {
readerMu := sync.Mutex{}
return &RWLockerImpl{
reader: sync.NewCond(&readerMu),
}
}
测试
读锁测试
func TestReaderLock() {
l := NewLocker()
wg := sync.WaitGroup{}
wg.Add(100)
for i := 0; i < 100; i++ {
go func(i int) {
l.RLock()
defer l.RUnlock()
fmt.Printf("%d", i)
fmt.Println()
wg.Done()
}(i)
}
wg.Wait()
}
这里截取部分输出结果进行展示
16
18
21
2324
435
2728
30
32
可见读锁之间是异步的
写锁测试
func TestWriterLock() {
l := NewLocker()
wg := sync.WaitGroup{}
wg.Add(100)
for i := 0; i < 100; i++ {
go func(i int) {
l.Lock()
defer l.Unlock()
fmt.Printf("%d--", i)
fmt.Println()
wg.Done()
}(i)
}
wg.Wait()
}
这里截取部分输出结果进行展示
0--
52--
57--
1--
5--
3--
4--
8--
6--
7--
10--
... ...
可见写锁之间是同步的
混合测试
func TestMixLock() {
l := NewLocker()
wg := sync.WaitGroup{}
wg.Add(100)
for i := 0; i < 100; i++ {
go func(i int) {
prefix := ""
if rand.Int()%2 == 1 {
l.Lock()
prefix = "w"
defer l.Unlock()
} else {
l.RLock()
prefix = "r"
defer l.RUnlock()
}
fmt.Printf("%s%d--", prefix, i)
fmt.Println()
wg.Done()
}(i)
}
wg.Wait()
}
这里截取部分输出结果进行展示
... ...
r68--
r72--
r79--
r84--
r93--
r95--
r96--
r8--r11--
r28--
r43--r42--r4--
... ...
w21--
w27--
w74--
w90--
w56--
w3--
... ...
结尾
作者能力有限,可能有考虑不周的情况出现,如有,希望指出,看到会及时改正。