rwlock
这一类并发读写问题叫作 readers-writers 问题,意思就是,同时可能有多个读或者多个
写,但是只要有一个线程在执行写操作,其它的线程都不能执行读写操作。
RWMutex 的方法也很少,总共有 5 个。
RWMutex 的零值是未加锁的状态,所以,当你使用 RWMutex 的时候,无论是声明变
量,还是嵌入到其它 struct 中,都不必显式地初始化。
我以计数器为例,来说明一下,如何使用 RWMutex 保护共享资源。计数器的
count++操作是写操作,而获取 count 的值是读操作,这个场景非常适合读写锁,因为读
操作可以并行执行,写操作时只允许一个线程执行,这正是 readers-writers 问题。
Lock/Unlock:写操作时调用的方法。如果锁已经被 reader 或者 writer 持有,那么,
Lock 方法会一直阻塞,直到能获取到锁;Unlock 则是配对的释放锁的方法。
RLock/RUnlock:读操作时调用的方法。如果锁已经被 writer 持有的话,RLock 方法
会一直阻塞,直到能获取到锁,否则就直接返回;而 RUnlock 是 reader 释放锁的方
法。
RLocker:这个方法的作用是为读操作返回一个 Locker 接口的对象。它的 Lock 方法会
调用 RWMutex 的 RLock 方法,它的 Unlock 方法会调用 RWMutex 的 RUnlock 方
法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
func main() {
var counter Counter
for i := 0; i < 10; i++ { // 10个reader
go func() {
for {
counter.Count() // 计数器读操作
time.Sleep(time.Millisecond)
}
}()
}
for { // 一个writer
counter.Incr() // 计数器写操作
time.Sleep(time.Second)
}
}
// 一个线程安全的计数器
type Counter struct {
mu sync.RWMutex
count uint64
}
// 使用写锁保护
func (c *Counter) Incr() {
c.mu.Lock()
c.count++
c.mu.Unlock()
}
// 使用读锁保护
func (c *Counter) Count() uint64 {
c.mu.RLock()
defer c.mu.RUnlock()
return c.count
}
|
如果你遇到可以明确区分 reader 和 writer goroutine 的场景,且有大量的并发读、少量
的并发写,并且有强烈的性能需求,你就可以考虑使用读写锁 RWMutex 替换 Mutex。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
func (rw *RWMutex) RLock() {
if atomic.AddInt32(&rw.readerCount, 1) < 0 {
// rw.readerCount是负值的时候,意味着此时有writer等待请求锁,因为writer优先
runtime_SemacquireMutex(&rw.readerSem, false, 0)
}
}
func (rw *RWMutex) RUnlock() {
if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {
rw.rUnlockSlow(r) // 有等待的writer
}
}
func (rw *RWMutex) rUnlockSlow(r int32) {
if atomic.AddInt32(&rw.readerWait, -1) == 0 {
// 最后一个reader了,writer终于有机会获得锁了
runtime_Semrelease(&rw.writerSem, false, 1)
}
}
|