MST

星途 面试题库

面试题:Go WaitGroup实现中的信号量机制及应用场景

Go的WaitGroup内部使用了类似信号量的机制,阐述该机制在WaitGroup中的具体实现方式,并举例说明在哪些复杂的并发场景下,这种信号量机制的WaitGroup能起到关键作用。
25.0万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

WaitGroup中类似信号量机制的具体实现方式

  1. 结构定义
    • WaitGroup结构体在Go源码中定义如下:
    type WaitGroup struct {
        noCopy noCopy
        state1 [3]uint32
    }
    
    • state1字段实际存储了两个重要信息:计数值(counter)和等待者数量(waiterCount),通过对这两个值的操作实现类似信号量机制。
  2. Add操作
    • Add(delta int)方法用于增加计数值。例如,在一个并发任务开始前,可以调用wg.Add(1),其中wgWaitGroup实例。
    • 它通过原子操作对state1中的计数值进行增加。源码中会检查是否有等待者(waiterCount),如果有等待者且计数值从0变为非0,会唤醒所有等待者。
  3. Done操作
    • Done()方法本质上是Add(-1),用于减少计数值。当一个并发任务完成时,调用wg.Done()
    • 同样是通过原子操作对计数值减1。如果计数值减为0,会唤醒所有等待WaitGroup的goroutine。
  4. Wait操作
    • Wait()方法会阻塞当前goroutine,直到WaitGroup的计数值变为0。
    • 它会原子地增加等待者数量,然后进入循环等待,直到计数值变为0。当计数值变为0时,会通过广播的方式唤醒所有等待的goroutine。

复杂并发场景举例

  1. 任务依赖场景
    • 假设我们有一个任务A,它依赖于任务B、C、D的完成。
    package main
    
    import (
        "fmt"
        "sync"
    )
    
    func main() {
        var wg sync.WaitGroup
        wg.Add(3)
    
        go func() {
            defer wg.Done()
            // 模拟任务B的执行
            fmt.Println("Task B is done")
        }()
    
        go func() {
            defer wg.Done()
            // 模拟任务C的执行
            fmt.Println("Task C is done")
        }()
    
        go func() {
            defer wg.Done()
            // 模拟任务D的执行
            fmt.Println("Task D is done")
        }()
    
        wg.Wait()
        fmt.Println("All tasks are done, Task A can start")
    }
    
    • 在这个场景中,WaitGroup的计数值初始化为3,每个子任务完成时调用Done()减少计数值。主线程通过Wait()等待所有子任务完成后再执行任务A。
  2. 并发请求与结果汇总场景
    • 假设有一个服务需要从多个不同的数据源获取数据,然后汇总这些数据。
    package main
    
    import (
        "fmt"
        "sync"
    )
    
    type DataSource struct {
        id int
    }
    
    func fetchData(source DataSource, wg *sync.WaitGroup, result *[]int) {
        defer wg.Done()
        // 模拟从数据源获取数据
        data := source.id * 10
        *result = append(*result, data)
    }
    
    func main() {
        var wg sync.WaitGroup
        var result []int
        dataSources := []DataSource{{1}, {2}, {3}}
    
        for _, source := range dataSources {
            wg.Add(1)
            go fetchData(source, &wg, &result)
        }
    
        wg.Wait()
        fmt.Println("All data fetched:", result)
    }
    
    • 这里每个fetchData goroutine负责从一个数据源获取数据,WaitGroup用于等待所有数据获取完成,然后汇总结果。