MST

星途 面试题库

面试题:Go 的 WaitGroup 在复杂业务逻辑中的优化应用

假设你正在开发一个分布式系统中的数据聚合模块,有多个数据源需要并发获取数据,获取数据的操作封装在不同的函数中,且获取数据的时间不定。获取完数据后,需要对这些数据进行汇总和分析。请使用 Go 的 WaitGroup 设计一个高效的实现方案,并阐述如何避免 WaitGroup 使用不当导致的死锁问题,同时说明在这个场景下 WaitGroup 的优势和局限性。
25.9万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

实现方案

package main

import (
    "fmt"
    "sync"
)

// 模拟从不同数据源获取数据的函数
func getData1(wg *sync.WaitGroup, result *[]int) {
    defer wg.Done()
    // 模拟获取数据的操作
    data := []int{1, 2, 3}
    *result = append(*result, data...)
}

func getData2(wg *sync.WaitGroup, result *[]int) {
    defer wg.Done()
    // 模拟获取数据的操作
    data := []int{4, 5, 6}
    *result = append(*result, data...)
}

func aggregateData() []int {
    var result []int
    var wg sync.WaitGroup

    wg.Add(2)
    go getData1(&wg, &result)
    go getData2(&wg, &result)

    wg.Wait()

    // 汇总和分析数据,这里简单示例,实际可进行复杂分析
    sum := 0
    for _, num := range result {
        sum += num
    }
    fmt.Printf("汇总结果: %d\n", sum)

    return result
}

避免死锁问题

  1. 确保 Add 和 Done 配对:在启动 goroutine 之前调用 wg.Add(n),其中 n 是需要等待的 goroutine 数量。在每个 goroutine 完成工作时调用 wg.Done()。确保 wg.Done() 调用在 goroutine 结束之前,通常使用 defer wg.Done() 来保证即使 goroutine 内部发生错误也能正确标记完成。
  2. 避免重复调用 Add:不要在 goroutine 内部再次调用 wg.Add,除非你清楚知道自己在做什么。如果需要动态增加等待的 goroutine 数量,要确保同步操作,避免竞争条件。
  3. 确保 Wait 在合适位置wg.Wait() 应该在所有启动的 goroutine 开始工作之后调用,并且在需要等待所有 goroutine 完成的逻辑位置调用,否则可能会提前返回导致数据未完全聚合。

WaitGroup 的优势

  1. 简单易用:Go 标准库提供的 sync.WaitGroup 结构简单,易于理解和使用,能够快速实现并发任务的等待同步。
  2. 性能高效:在底层实现上,WaitGroup 使用信号量机制,能够高效地进行同步操作,不会引入过多的性能开销,适合高并发场景。
  3. 适合简单场景:对于只需要等待一组 goroutine 完成的简单并发场景,WaitGroup 提供了足够的功能,不需要引入复杂的同步机制。

WaitGroup 的局限性

  1. 功能单一WaitGroup 仅能用于等待一组 goroutine 完成,对于更复杂的同步需求,如条件变量、读写锁等场景,WaitGroup 无法满足。
  2. 缺乏灵活性WaitGroup 一旦通过 Add 设置了等待的 goroutine 数量,就不能动态地修改这个数量(除了使用一些非标准的方式),在需要动态调整并发任务数量的场景下不够灵活。
  3. 错误处理不便WaitGroup 本身没有提供处理 goroutine 内部错误的机制,如果某个 goroutine 发生错误,WaitGroup 无法感知并做出相应处理,需要额外的错误处理逻辑。