面试题答案
一键面试package main
import (
"fmt"
"sync"
"time"
)
// 自定义读写锁结构体
type MyRWMutex struct {
mu sync.Mutex
readers int
writers int
writeCond *sync.Cond
readCond *sync.Cond
}
// 初始化自定义读写锁
func NewMyRWMutex() *MyRWMutex {
m := &MyRWMutex{}
m.writeCond = sync.NewCond(&m.mu)
m.readCond = sync.NewCond(&m.mu)
return m
}
// 读锁定
func (rw *MyRWMutex) RLock() {
rw.mu.Lock()
for rw.writers > 0 { // 如果有写操作正在进行或等待,读操作等待
rw.readCond.Wait()
}
rw.readers++
rw.mu.Unlock()
}
// 读解锁
func (rw *MyRWMutex) RUnlock() {
rw.mu.Lock()
rw.readers--
if rw.readers == 0 { // 如果没有读操作了,唤醒等待的写操作
rw.writeCond.Broadcast()
}
rw.mu.Unlock()
}
// 写锁定
func (rw *MyRWMutex) Lock() {
rw.mu.Lock()
rw.writers++
for rw.readers > 0 || rw.writers > 1 { // 如果有读操作正在进行或者有其他写操作正在等待,写操作等待
rw.writeCond.Wait()
}
rw.mu.Unlock()
}
// 写解锁
func (rw *MyRWMutex) Unlock() {
rw.mu.Lock()
rw.writers--
if rw.writers == 0 { // 如果没有写操作了,唤醒等待的读操作和写操作
rw.writeCond.Broadcast()
rw.readCond.Broadcast()
}
rw.mu.Unlock()
}
在这个实现中:
MyRWMutex
结构体包含了一个基础的互斥锁mu
,用于保护读写状态的修改。readers
记录当前正在进行的读操作数量,writers
记录当前正在进行的写操作数量或等待写操作的数量。writeCond
和readCond
分别是用于写操作和读操作等待与唤醒的条件变量。
在读锁定 RLock
方法中,当有写操作正在进行或等待时,读操作会等待,直到没有写操作。读解锁 RUnlock
方法中,当所有读操作结束,会唤醒等待的写操作。
在写锁定 Lock
方法中,当有读操作正在进行或者有其他写操作正在等待时,写操作会等待。写解锁 Unlock
方法中,当写操作结束,会唤醒所有等待的读操作和写操作。这样确保了写操作不会饥饿。
下面是一个简单的测试示例:
func main() {
var wg sync.WaitGroup
rw := NewMyRWMutex()
// 启动读操作
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
rw.RLock()
fmt.Printf("Reader %d is reading\n", id)
time.Sleep(100 * time.Millisecond)
rw.RUnlock()
}(i)
}
// 启动写操作
for i := 0; i < 3; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
rw.Lock()
fmt.Printf("Writer %d is writing\n", id)
time.Sleep(200 * time.Millisecond)
rw.Unlock()
}(i)
}
wg.Wait()
}
在这个测试示例中,启动了多个读操作和写操作,以模拟高并发读写场景,验证自定义读写锁是否能避免写饥饿。