面试题答案
一键面试WaitGroup中类似信号量机制的具体实现方式
- 结构定义:
WaitGroup
结构体在Go源码中定义如下:
type WaitGroup struct { noCopy noCopy state1 [3]uint32 }
state1
字段实际存储了两个重要信息:计数值(counter
)和等待者数量(waiterCount
),通过对这两个值的操作实现类似信号量机制。
- Add操作:
Add(delta int)
方法用于增加计数值。例如,在一个并发任务开始前,可以调用wg.Add(1)
,其中wg
是WaitGroup
实例。- 它通过原子操作对
state1
中的计数值进行增加。源码中会检查是否有等待者(waiterCount
),如果有等待者且计数值从0变为非0,会唤醒所有等待者。
- Done操作:
Done()
方法本质上是Add(-1)
,用于减少计数值。当一个并发任务完成时,调用wg.Done()
。- 同样是通过原子操作对计数值减1。如果计数值减为0,会唤醒所有等待
WaitGroup
的goroutine。
- Wait操作:
Wait()
方法会阻塞当前goroutine,直到WaitGroup
的计数值变为0。- 它会原子地增加等待者数量,然后进入循环等待,直到计数值变为0。当计数值变为0时,会通过广播的方式唤醒所有等待的goroutine。
复杂并发场景举例
- 任务依赖场景:
- 假设我们有一个任务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。
- 并发请求与结果汇总场景:
- 假设有一个服务需要从多个不同的数据源获取数据,然后汇总这些数据。
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
用于等待所有数据获取完成,然后汇总结果。