MST

星途 面试题库

面试题:Go中WaitGroup在高并发资源竞争与控制场景的应用

在一个高并发场景中,有多个goroutine需要访问和修改共享资源(如一个计数器),同时需要使用WaitGroup等待所有操作完成。但由于资源竞争可能导致数据不一致问题。请设计一个解决方案,不仅要正确使用WaitGroup来同步goroutine,还要通过合适的机制(如互斥锁等)解决资源竞争问题,分析在高并发下这种解决方案的性能瓶颈及可能的优化方向。
16.1万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

解决方案代码示例

package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    var mu sync.Mutex
    counter := 0

    numGoroutines := 10
    for i := 0; i < numGoroutines; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            mu.Lock()
            counter++
            mu.Unlock()
        }()
    }

    wg.Wait()
    fmt.Println("Final counter value:", counter)
}

方案解析

  1. 使用 sync.WaitGroup 同步 goroutine:通过 wg.Add(1) 为每个 goroutine 增加计数,在 goroutine 结束时调用 wg.Done() 减少计数,最后通过 wg.Wait() 阻塞主 goroutine 直到所有计数为0,即所有 goroutine 完成。
  2. 使用 sync.Mutex 解决资源竞争:在访问和修改共享资源 counter 前,通过 mu.Lock() 加锁,操作完成后通过 mu.Unlock() 解锁,确保同一时间只有一个 goroutine 可以访问和修改共享资源。

性能瓶颈分析

  1. 锁争用:当有大量 goroutine 频繁竞争锁时,会导致许多 goroutine 处于等待状态,增加上下文切换开销,降低系统整体性能。
  2. 串行化操作:由于互斥锁的存在,对共享资源的操作变为串行化,无法充分利用多核 CPU 的并行处理能力。

优化方向

  1. 减少锁粒度:如果共享资源可以进一步细分,可以为每个子资源设置单独的锁,从而减少锁争用。例如,如果计数器是由多个部分组成,可以为每个部分设置锁。
  2. 读写锁:如果读操作远多于写操作,可以使用 sync.RWMutex,读操作时允许多个 goroutine 同时进行,写操作时加写锁保证数据一致性。
  3. 无锁数据结构:使用无锁数据结构,如 sync/atomic 包中的原子操作,对于简单的计数器等场景,可以避免锁的开销。例如:
package main

import (
    "fmt"
    "sync"
    "sync/atomic"
)

func main() {
    var wg sync.WaitGroup
    var counter uint64

    numGoroutines := 10
    for i := 0; i < numGoroutines; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            atomic.AddUint64(&counter, 1)
        }()
    }

    wg.Wait()
    fmt.Println("Final counter value:", atomic.LoadUint64(&counter))
}
  1. 使用分布式计算:对于非常高并发的场景,可以将任务分发到多个节点上进行处理,减少单个节点的压力。