面试题答案
一键面试闭包递归在Go并发编程中的挑战
- 数据竞争:
- 当闭包递归调用涉及共享变量时,多个并发执行的闭包可能同时读写这些变量,导致数据竞争。例如,在一个闭包递归计算任务中,如果多个并发的递归分支都要更新一个全局的结果变量,就会出现数据竞争。
- 资源同步:
- 递归调用可能需要获取和释放资源(如文件句柄、数据库连接等)。在并发环境下,如果资源同步不当,可能导致资源泄漏或不一致。例如,在递归遍历分布式文件系统时,若没有正确同步文件句柄的获取和释放,可能导致文件句柄占用过多或文件操作异常。
- 栈溢出:虽然Go语言使用的是协作式调度(goroutine),但如果闭包递归深度过大,仍可能导致栈溢出问题,特别是在每个递归调用都分配较多栈空间的情况下。
实际场景及解决方案
场景:分布式计算任务划分
假设我们有一个分布式计算任务,需要将一个大任务划分成多个小任务,然后递归地并行处理这些小任务,最终汇总结果。
代码示例
package main
import (
"fmt"
"sync"
)
// 任务结构体
type Task struct {
ID int
Data int
}
// 执行任务的函数
func executeTask(task Task) int {
// 模拟任务执行
return task.Data * 2
}
// 递归划分任务并并发执行
func recursiveParallelTask(task Task, wg *sync.WaitGroup, resultChan chan int) {
defer wg.Done()
// 简单模拟任务划分,当任务数据大于10时划分
if task.Data > 10 {
subTask1 := Task{ID: task.ID * 10 + 1, Data: task.Data / 2}
subTask2 := Task{ID: task.ID * 10 + 2, Data: task.Data - task.Data/2}
var subWG sync.WaitGroup
subWG.Add(2)
go recursiveParallelTask(subTask1, &subWG, resultChan)
go recursiveParallelTask(subTask2, &subWG, resultChan)
go func() {
subWG.Wait()
close(resultChan)
}()
} else {
result := executeTask(task)
resultChan <- result
}
}
func main() {
initialTask := Task{ID: 1, Data: 20}
var wg sync.WaitGroup
wg.Add(1)
resultChan := make(chan int)
go recursiveParallelTask(initialTask, &wg, resultChan)
var totalResult int
for result := range resultChan {
totalResult += result
}
wg.Wait()
fmt.Printf("Total result: %d\n", totalResult)
}
代码说明
- 任务定义:
Task
结构体定义了任务的ID和数据。 - 任务执行:
executeTask
函数模拟单个任务的执行。 - 递归并行任务:
recursiveParallelTask
函数实现了闭包递归,当任务数据大于10时,将任务划分为两个子任务并并发执行。通过sync.WaitGroup
和chan
来处理并发同步和结果收集,避免了数据竞争和资源同步问题。 - 主函数:初始化任务并启动递归并行任务,最后收集并打印结果。