编程后端开发工程师

在 Go 中如何实现闭包 (closures),在循环内启动 goroutine 时与它们使用相关的潜在问题,以及如何避免典型的错误?

用 Hintsage AI 助手通过面试

答案

在 Go 中,闭包 (closures) 是捕获其周围作用域变量的函数。闭包最常用于在其他函数内创建匿名函数。

使用闭包时最常见的问题是,在 goroutine 内部使用循环变量时会出现意想不到的行为:

for i := 0; i < 3; i++ { go func() { fmt.Println(i) }() }

每个 goroutine 可能会打印出相同的值 i,因为变量 i 是循环中的,闭包捕获的正是变量,而不是它在每次迭代中的值。

正确的方法:

for i := 0; i < 3; i++ { go func(val int) { fmt.Println(val) }(i) }

这种行为与闭包持有对变量的引用(地址),而不是它的切片(by value)值有关。

有陷阱的问题

如果多个 goroutine 在循环内捕获循环变量,它们将打印出什么值?

答案: 所有的 goroutine 可能会打印出相同的值(通常是最后一个),因为它们看到的是变量的当前值,而在 goroutine 执行时,循环已经结束。为了避免这种情况,必须将变量作为参数传递给闭包。

示例:

for i := 0; i < 5; i++ { go func() { fmt.Println(i) }() } // 很可能得到:5 5 5 5 5

由于对主题细节不知情而导致的实际错误示例


故事

在产品分析系统中,数据在并行 goroutine 中通过捕获循环变量的闭包进行匿名化,结果是所有并行任务处理相同的数据集——最终导致统计数据失真和报告不准确。

故事

在云服务的 Go 集成后端中,团队决定优化指标收集,通过循环中的匿名函数启动处理——在 goroutine 中捕获了 map 的索引,结果是部分处理程序收集的数据不是为其服务,而是为最后处理的索引。

故事

在快递初创公司中,不正确使用闭包更新订单坐标导致最后一个订单的坐标在切片中被批量更新,而不是当前的——这是由于在匿名函数内部访问切片时发生的竞态。