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)
}
}