MST

星途 面试题库

面试题:Go信号量在高并发读写场景下的优化策略

在一个高并发场景中,有多个协程同时对一个共享数据结构进行读写操作,使用Go信号量来控制并发访问。请设计并实现一个读写锁机制,要求读操作可以并发执行,但写操作时需要独占资源,且尽量减少读操作被写操作阻塞的时间。阐述你的设计思路,并给出关键代码实现。
48.8万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

设计思路

  1. 使用Go语言的sync包中的semaphore来实现信号量控制。
  2. 定义两个信号量,一个用于读操作(readSem),一个用于写操作(writeSem)。
  3. 读操作时,获取readSem信号量,允许多个读操作并发进行。写操作时,获取writeSem信号量,由于写操作需要独占资源,所以该信号量初始值为1。
  4. 为了减少读操作被写操作阻塞的时间,在写操作开始前,先获取所有读操作的信号量,确保没有新的读操作开始,然后再进行写操作。写操作完成后,释放所有读操作的信号量。

关键代码实现

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()
}