MST

星途 面试题库

面试题:Go中WaitGroup的深度理解与优化

在一个高并发的Go程序中,有大量的goroutine需要使用WaitGroup来同步。在实际运行过程中发现性能瓶颈,怀疑是WaitGroup的使用方式不当。请分析可能存在的问题,并提出优化方案,同时用代码示例说明优化前后的效果。
19.9万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

可能存在的问题

  1. 不必要的等待:如果在某些goroutine完成工作后,其他goroutine还在等待不必要的操作,会导致整体性能下降。例如,有些goroutine已经完成了核心任务,但因为WaitGroup还未释放,仍然处于等待状态。
  2. 过大的粒度:如果将大量不同功能、不同重要性的goroutine都放在同一个WaitGroup下,可能导致整体同步过于粗粒度,部分goroutine的阻塞会影响到其他已经完成的goroutine。
  3. 频繁的创建和销毁:如果在高并发场景下频繁地创建和销毁WaitGroup实例,会增加系统开销。

优化方案

  1. 分拆WaitGroup:根据功能或者执行时间将goroutine分组,使用多个WaitGroup进行同步,这样不同组的goroutine可以独立完成和等待,减少不必要的等待时间。
  2. 优化等待逻辑:在确保业务逻辑正确的前提下,尽可能让goroutine完成任务后尽早释放WaitGroup,避免不必要的等待。
  3. 复用WaitGroup:避免频繁创建和销毁WaitGroup实例,通过复用减少系统开销。

代码示例

package main

import (
    "fmt"
    "sync"
    "time"
)

// 优化前
func unoptimized() {
    var wg sync.WaitGroup
    numGoroutines := 1000

    for i := 0; i < numGoroutines; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            // 模拟一些工作
            time.Sleep(10 * time.Millisecond)
        }()
    }

    start := time.Now()
    wg.Wait()
    elapsed := time.Since(start)
    fmt.Printf("Unoptimized took %s\n", elapsed)
}

// 优化后
func optimized() {
    numGroups := 10
    var wgs [10]sync.WaitGroup
    numGoroutines := 1000
    goroutinesPerGroup := numGoroutines / numGroups

    for group := 0; group < numGroups; group++ {
        for i := 0; i < goroutinesPerGroup; i++ {
            wgs[group].Add(1)
            go func(g int) {
                defer wgs[g].Done()
                // 模拟一些工作
                time.Sleep(10 * time.Millisecond)
            }(group)
        }
    }

    start := time.Now()
    for group := 0; group < numGroups; group++ {
        wgs[group].Wait()
    }
    elapsed := time.Since(start)
    fmt.Printf("Optimized took %s\n", elapsed)
}

func main() {
    unoptimized()
    optimized()
}

在上述代码中,unoptimized函数使用一个WaitGroup来同步所有的goroutine。而optimized函数将goroutine分成10组,每组使用一个WaitGroup,这样不同组的goroutine可以独立完成任务并等待,减少了整体的等待时间。运行结果可以看到优化后的版本执行时间更短,性能得到提升。