1. 饥饿问题产生原因
- 读锁特性:
RWMutex
允许多个读操作同时进行,读锁不互斥。当有读操作在进行时,新的读操作可以不断获取读锁。
- 写锁互斥:写锁与读锁、写锁与写锁之间是互斥的。如果在写操作等待获取锁的过程中,不断有新的读操作到来并获取读锁,写操作就会一直处于等待状态,从而导致写操作饥饿。
例如,假设当前有一个写操作
W1
等待获取锁,此时陆续来了读操作R1
、R2
、R3
等,由于读锁可以并发获取,这些读操作会依次获取读锁并执行,而W1
只能一直等待,若读操作源源不断,W1
就会长时间无法获取锁。
2. 缓解或解决策略
- 公平锁策略:
- 原理:实现一个公平的锁机制,按照请求锁的先后顺序来分配锁。可以通过一个队列来记录请求锁的顺序,当锁可用时,按照队列顺序依次分配。
- 示例代码:
package main
import (
"fmt"
"sync"
)
type FairRWMutex struct {
mu sync.Mutex
readers int
writers int
waiters int
readerCh chan struct{}
writerCh chan struct{}
}
func NewFairRWMutex() *FairRWMutex {
return &FairRWMutex{
readerCh: make(chan struct{}, 1),
writerCh: make(chan struct{}, 1),
}
}
func (rw *FairRWMutex) RLock() {
rw.mu.Lock()
if rw.writers > 0 || rw.waiters > 0 {
rw.waiters++
rw.mu.Unlock()
<-rw.readerCh
rw.mu.Lock()
rw.waiters--
}
rw.readers++
rw.mu.Unlock()
}
func (rw *FairRWMutex) RUnlock() {
rw.mu.Lock()
rw.readers--
if rw.readers == 0 && rw.waiters > 0 {
select {
case rw.writerCh <- struct{}{}:
default:
}
}
rw.mu.Unlock()
}
func (rw *FairRWMutex) Lock() {
rw.mu.Lock()
if rw.readers > 0 || rw.writers > 0 {
rw.waiters++
rw.mu.Unlock()
<-rw.writerCh
rw.mu.Lock()
rw.waiters--
}
rw.writers++
rw.mu.Unlock()
}
func (rw *FairRWMutex) Unlock() {
rw.mu.Lock()
rw.writers--
if rw.waiters > 0 {
select {
case rw.writerCh <- struct{}{}:
default:
select {
case rw.readerCh <- struct{}{}:
default:
}
}
}
rw.mu.Unlock()
}
- 写优先策略:
- 原理:在每次读操作获取锁之前,检查是否有写操作在等待。如果有写操作等待,则读操作等待,优先让写操作获取锁。
- 示例代码:
package main
import (
"fmt"
"sync"
)
type WritePreferRWMutex struct {
mu sync.Mutex
readers int
writers int
writeWait int
}
func (rw *WritePreferRWMutex) RLock() {
rw.mu.Lock()
for rw.writers > 0 || rw.writeWait > 0 {
rw.mu.Unlock()
rw.mu.Lock()
}
rw.readers++
rw.mu.Unlock()
}
func (rw *WritePreferRWMutex) RUnlock() {
rw.mu.Lock()
rw.readers--
rw.mu.Unlock()
}
func (rw *WritePreferRWMutex) Lock() {
rw.mu.Lock()
rw.writeWait++
for rw.readers > 0 || rw.writers > 0 {
rw.mu.Unlock()
rw.mu.Lock()
}
rw.writers++
rw.writeWait--
rw.mu.Unlock()
}
func (rw *WritePreferRWMutex) Unlock() {
rw.mu.Lock()
rw.writers--
rw.mu.Unlock()
}
- 定期提升写操作优先级:
- 原理:设置一个定时器或者计数器,当写操作等待时间超过一定阈值或者写操作等待次数达到一定数量时,强制暂停读操作,优先让写操作获取锁。
- 示例:在一个服务中,记录写操作等待时间,若超过100毫秒,在下次锁释放时,优先分配给写操作。可以通过在锁的获取和释放逻辑中添加时间记录和判断逻辑来实现。