MST
星途 面试题库

面试题:Go中Goroutine资源回收与竞争

假设你有多个Goroutine在并发读写共享资源,在Goroutine生命周期结束时,如何确保资源的正确回收并且避免数据竞争问题?请结合`sync`包相关工具阐述你的解决方案。
20.1万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试
  1. 使用sync.Mutex保护共享资源
    • 在读写共享资源时,通过sync.Mutex来加锁。读操作和写操作前都获取锁,操作完成后释放锁。这样可以避免数据竞争。例如:
    package main
    
    import (
        "fmt"
        "sync"
    )
    
    var (
        data int
        mu   sync.Mutex
    )
    
    func writer(wg *sync.WaitGroup) {
        defer wg.Done()
        mu.Lock()
        data++
        mu.Unlock()
    }
    
    func reader(wg *sync.WaitGroup) {
        defer wg.Done()
        mu.Lock()
        fmt.Println("Read data:", data)
        mu.Unlock()
    }
    
    func main() {
        var wg sync.WaitGroup
        for i := 0; i < 5; i++ {
            wg.Add(1)
            go writer(&wg)
        }
        for i := 0; i < 5; i++ {
            wg.Add(1)
            go reader(&wg)
        }
        wg.Wait()
    }
    
  2. 使用sync.RWMutex(适用于读多写少场景)
    • 读操作使用读锁(RLock),写操作使用写锁(Lock)。读锁允许多个读操作同时进行,写锁则独占资源,避免读写冲突和写写冲突。例如:
    package main
    
    import (
        "fmt"
        "sync"
    )
    
    var (
        data int
        mu   sync.RWMutex
    )
    
    func writer(wg *sync.WaitGroup) {
        defer wg.Done()
        mu.Lock()
        data++
        mu.Unlock()
    }
    
    func reader(wg *sync.WaitGroup) {
        defer wg.Done()
        mu.RLock()
        fmt.Println("Read data:", data)
        mu.RUnlock()
    }
    
    func main() {
        var wg sync.WaitGroup
        for i := 0; i < 5; i++ {
            wg.Add(1)
            go writer(&wg)
        }
        for i := 0; i < 5; i++ {
            wg.Add(1)
            go reader(&wg)
        }
        wg.Wait()
    }
    
  3. 使用sync.WaitGroup等待所有Goroutine结束
    • 在主Goroutine中创建一个sync.WaitGroup实例。每个Goroutine开始前调用wg.Add(1),在Goroutine结束时调用wg.Done()。主Goroutine通过wg.Wait()等待所有Goroutine完成。这样可以确保在共享资源不再被使用时进行回收。如上述示例中,在main函数中创建wg并在每个Goroutine中相应调用AddDoneWait方法。
  4. 使用sync.Cond(可选,用于更复杂的同步场景)
    • 当需要在满足某个条件时才进行资源回收或某些操作时,可以使用sync.Condsync.Cond需要与一个Locker(如sync.Mutex)结合使用。例如,假设资源需要在所有Goroutine完成某种特定操作后才能回收:
    package main
    
    import (
        "fmt"
        "sync"
    )
    
    var (
        data    int
        mu      sync.Mutex
        cond    *sync.Cond
        counter int
    )
    
    func worker(wg *sync.WaitGroup) {
        defer wg.Done()
        mu.Lock()
        // 模拟一些操作
        data++
        counter++
        if counter == 10 {
            cond.Broadcast()
        }
        mu.Unlock()
    }
    
    func main() {
        var wg sync.WaitGroup
        cond = sync.NewCond(&mu)
        for i := 0; i < 10; i++ {
            wg.Add(1)
            go worker(&wg)
        }
        mu.Lock()
        for counter < 10 {
            cond.Wait()
        }
        // 在这里可以安全地回收资源
        fmt.Println("All workers finished, safe to recycle resources.")
        mu.Unlock()
        wg.Wait()
    }
    

通过以上sync包工具的合理使用,可以确保在Goroutine生命周期结束时共享资源的正确回收并避免数据竞争问题。