MST

星途 面试题库

面试题:Go信号量实现中如何处理资源竞争

在Go信号量的实际实现过程中,资源竞争是不可避免的问题。假设你正在实现一个支持多个协程并发访问共享资源的信号量机制,描述你会采取哪些措施来确保在高并发场景下信号量对共享资源的访问控制是线程安全的,并且不会出现死锁或资源泄漏等问题。
11.7万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试
  1. 使用sync.Mutex保护信号量状态
    • 定义一个结构体来表示信号量,其中包含信号量的当前值和一个互斥锁。
    type Semaphore struct {
        value int
        mutex sync.Mutex
    }
    
    • 在获取(Acquire)和释放(Release)信号量的方法中,使用互斥锁来保护对信号量值的修改。
    func (s *Semaphore) Acquire() {
        s.mutex.Lock()
        for s.value <= 0 {
            s.mutex.Unlock()
            runtime.Gosched()
            s.mutex.Lock()
        }
        s.value--
        s.mutex.Unlock()
    }
    
    func (s *Semaphore) Release() {
        s.mutex.Lock()
        s.value++
        s.mutex.Unlock()
    }
    
  2. 避免死锁
    • 合理的锁顺序:在涉及多个信号量或锁的情况下,确保所有协程以相同的顺序获取锁。例如,如果有两个信号量sema1sema2,所有协程都应该先获取sema1,再获取sema2
    • 超时机制:在获取信号量时设置超时。可以使用context.Context来实现这一点。
    func (s *Semaphore) AcquireWithTimeout(ctx context.Context) bool {
        s.mutex.Lock()
        for s.value <= 0 {
            s.mutex.Unlock()
            select {
            case <-ctx.Done():
                return false
            default:
                runtime.Gosched()
            }
            s.mutex.Lock()
        }
        s.value--
        s.mutex.Unlock()
        return true
    }
    
  3. 防止资源泄漏
    • 确保释放:在获取信号量后,使用Go语言的defer语句确保无论获取信号量后的代码路径如何,信号量都会被正确释放。
    func someFunction(s *Semaphore) {
        s.Acquire()
        defer s.Release()
        // 访问共享资源的代码
    }
    
    • 异常处理:如果在获取信号量后发生异常,要保证信号量能够被正确释放。Go语言中虽然没有传统的异常处理机制,但可以通过recover来处理panic,并在defer中释放信号量。
    func someFunctionWithPanic(s *Semaphore) {
        s.Acquire()
        defer func() {
            if r := recover(); r != nil {
                s.Release()
                panic(r)
            }
        }()
        // 可能发生panic的代码
    }