面试题答案
一键面试设计思路
- 使用Go语言的
sync
包中的semaphore
来实现信号量控制。 - 定义两个信号量,一个用于读操作(
readSem
),一个用于写操作(writeSem
)。 - 读操作时,获取
readSem
信号量,允许多个读操作并发进行。写操作时,获取writeSem
信号量,由于写操作需要独占资源,所以该信号量初始值为1。 - 为了减少读操作被写操作阻塞的时间,在写操作开始前,先获取所有读操作的信号量,确保没有新的读操作开始,然后再进行写操作。写操作完成后,释放所有读操作的信号量。
关键代码实现
package main
import (
"context"
"fmt"
"sync"
"time"
"golang.org/x/sync/semaphore"
)
type RWLock struct {
readSem *semaphore.Weighted
writeSem *semaphore.Weighted
readers int
}
func NewRWLock() *RWLock {
return &RWLock{
readSem: semaphore.NewWeighted(1000), // 允许大量读操作并发
writeSem: semaphore.NewWeighted(1), // 只允许一个写操作
readers: 0,
}
}
func (rw *RWLock) RLock() {
// 获取读信号量
if err := rw.readSem.Acquire(context.Background(), 1); err != nil {
panic(err)
}
rw.readers++
if rw.readers == 1 {
// 如果是第一个读操作,获取写信号量,防止写操作开始
if err := rw.writeSem.Acquire(context.Background(), 1); err != nil {
panic(err)
}
}
// 释放读信号量,允许其他读操作
rw.readSem.Release(1)
}
func (rw *RWLock) RUnlock() {
rw.readers--
if rw.readers == 0 {
// 如果没有读操作了,释放写信号量
rw.writeSem.Release(1)
}
}
func (rw *RWLock) Lock() {
// 获取所有读操作的信号量,防止新的读操作开始
if err := rw.readSem.Acquire(context.Background(), 1000); err != nil {
panic(err)
}
// 获取写信号量
if err := rw.writeSem.Acquire(context.Background(), 1); err != nil {
panic(err)
}
}
func (rw *RWLock) Unlock() {
// 释放写信号量
rw.writeSem.Release(1)
// 释放所有读操作的信号量
rw.readSem.Release(1000)
}
你可以使用以下方式测试上述代码:
func main() {
var wg sync.WaitGroup
rw := NewRWLock()
sharedData := 0
// 模拟读操作
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
rw.RLock()
fmt.Printf("Reader %d reading: %d\n", i, sharedData)
time.Sleep(100 * time.Millisecond)
rw.RUnlock()
}()
}
// 模拟写操作
wg.Add(1)
go func() {
defer wg.Done()
rw.Lock()
sharedData++
fmt.Println("Writer writing:", sharedData)
time.Sleep(200 * time.Millisecond)
rw.Unlock()
}()
wg.Wait()
}