面试题答案
一键面试Go语言中RWMutex实现原理
- 内部数据结构
RWMutex
结构体定义在src/sync/rwmutex.go
中,主要包含两个字段:state
:一个uint32
类型的变量,用于表示锁的状态。其中低16位表示写锁的计数(正常情况下为0或1,当有写锁等待时可能大于1),高16位表示读锁的计数。sema
:一个信号量,用于阻塞和唤醒等待的协程。写锁等待时会通过runtime_Semacquire
获取信号量,读锁等待时也类似。
- 状态机设计
- 初始状态:
state
为0,此时没有读锁和写锁被持有。 - 读锁获取:
- 当一个协程尝试获取读锁时,它会先检查
state
的低16位(写锁计数)。如果写锁计数为0(即没有写锁被持有),则将state
的高16位(读锁计数)加1,成功获取读锁。 - 如果写锁计数不为0,说明有写锁被持有或者有写锁在等待,此时读锁获取失败,该协程会被阻塞并加入到等待队列(通过信号量
sema
)。
- 当一个协程尝试获取读锁时,它会先检查
- 写锁获取:
- 当一个协程尝试获取写锁时,它首先会尝试原子地将
state
的低16位加1。如果加1成功且此时state
的高16位(读锁计数)为0,说明获取写锁成功。 - 如果加1失败(说明已有写锁被持有),或者加1成功但读锁计数不为0,写锁获取失败,该协程会被阻塞并加入到等待队列(通过信号量
sema
)。当写锁释放时,会唤醒等待队列中的写锁等待者(如果有)。
- 当一个协程尝试获取写锁时,它首先会尝试原子地将
- 读锁释放:将
state
的高16位(读锁计数)减1。如果此时读锁计数为0且有写锁等待(state
低16位大于1),会唤醒一个写锁等待者。 - 写锁释放:将
state
的低16位(写锁计数)减1。如果此时有写锁等待(state
低16位大于0),会唤醒一个写锁等待者;如果有读锁等待,会唤醒所有读锁等待者。
- 初始状态:
自定义改进思路
- 优先写锁调度
- 思路:在当前实现中,读锁的优先级相对较高,因为只要没有写锁持有,读锁就可以不断获取。对于读多写少且写操作需快速响应的场景,可以引入一个写优先队列。当有写操作请求时,将其加入到写优先队列。读锁获取时,除了检查写锁状态,还需要检查写优先队列是否为空。如果不为空,读锁等待,直到写优先队列处理完。
- 实现:可以使用一个
channel
来模拟写优先队列。在RWMutex
结构体中新增一个writeQueue chan struct{}
字段。在获取读锁时,增加对writeQueue
是否为空的检查逻辑。在写锁请求时,向writeQueue
发送一个信号。
- 减少读锁阻塞写锁的时间
- 思路:可以采用一种机制,在写锁请求到达时,逐渐减少读锁的允许获取数量。例如,设置一个阈值,当写锁等待时间超过一定阈值时,开始限制新的读锁获取。
- 实现:在
RWMutex
结构体中新增一个字段记录写锁开始等待的时间,以及一个阈值字段。在获取读锁时,检查写锁等待时间是否超过阈值,如果超过,则根据一定策略(如限制读锁获取数量为当前读锁数量的一半)来限制新的读锁获取。
- 读写锁分离队列
- 思路:将读锁等待队列和写锁等待队列分开管理。这样可以更灵活地控制读写锁的唤醒策略。例如,当写锁释放时,可以优先唤醒写锁等待队列中的协程,而不是像原实现那样可能唤醒读锁等待者。
- 实现:在
RWMutex
结构体中新增两个信号量字段,分别用于读锁等待队列和写锁等待队列。在获取锁失败时,根据锁的类型加入对应的等待队列。在释放锁时,根据队列情况优先唤醒相应队列中的协程。