golang 读写锁 sync.RWMutex
之前的博客讲了使用互斥锁 sync.Mutex 来保证同一个时刻只有一个goroutine可以访问临界区(内部有共享变量)。当时更多时候,goroutine不是要修改共享变量,只是要读取就行,而互斥锁是完全互斥的。针对这种情况,golang中提供了读写锁 **sync.RWMutex ** ——它 不限制资源的并发读,但是读写、写写操作无法并行执行,这种锁叫作“多读单写”锁(multiple readers, single writer lock)。
- 写操作使用
sync.RWMutex.Lock
和sync.RWMutex.Unlock
方法; - 读操作使用
sync.RWMutex.RLock
和sync.RWMutex.RUnlock
方法;
写锁
加写锁
当资源的使用者想要获取写锁时,需要调用 sync.RWMutex.Lock
方法:
- 阻塞后续的写操作
- 阻塞后续的读操作
- 如果仍然有其他 Goroutine 持有互斥锁的读锁,该 Goroutine 进入休眠状态等待所有读锁所有者执行结束后,将当前协程唤醒
- 获取写锁时会先阻塞写锁的获取,后阻塞读锁的获取,这种策略能够保证读操作不会被连续的写操作『饿死』。
解写锁
写锁的释放会调用 sync.RWMutex.Unlock
:
- 调用
sync/atomic.AddInt32
函数将readerCount
变回正数,释放读锁; - 通过 for 循环释放所有因为获取读锁而陷入等待的 Goroutine:
- 调用
sync.Mutex.Unlock
释放写锁;
读锁
读锁的加锁方法 sync.RWMutex.RLock
很简单,该方法会通过 sync/atomic.AddInt32
将 readerCount
加一:
- 如果其他 Goroutine 获得了写锁,当前 Goroutine 就会陷入休眠等待锁的释放;
当 Goroutine 想要释放读锁时,会调用 sync.RWMutex.RUnlock
方法
读锁和写锁的关系:
- 调用
sync.RWMutex.Lock
尝试获取写锁时;- 每次
sync.RWMutex.RUnlock
都会将readerCount
其减一,当它归零时该 Goroutine 会获得写锁; - 将
readerCount
减少rwmutexMaxReaders
个数以阻塞后续的读操作;
- 每次
- 调用
sync.RWMutex.Unlock
释放写锁时,会先通知所有的读操作,然后才会释放持有的互斥锁;