持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情
PS:已经更文多少天,N就写几。一定要写对文案,否则文章不计入在内
for循环并发执行闭包函数会发生怎样的线上惨剧?资源泄漏,计费亏损?本文从匿名函数、闭包函数区分说起,解析了for-range中并发执行闭包的坑和解决办法。
先来看下以下代码输出是什么:
//程序一
values := []string{"a", "b", "c"}
for _, val := range values {
go func() {
fmt.Println(val)
}()
}
//程序二
values := []string{"a", "b", "c"}
for _, val := range values {
func() {
fmt.Println(val)
}()
}
如果很清楚知道输出并知道为什么这样输出,那可以直接跳过本文。
匿名函数和闭包函数:
// 具名函数
func Add(a, b int) int {
return a+b
}
// 匿名函数
var Add = func(a, b int) int {
return a+b
}
//闭包函数
func main() {
for i := 0; i < 3; i++ {
defer func(){ println(i) } ()
}
}
// Output:
// 3
// 3
// 3
闭包函数:匿名函数捕获了外部函数的局部变量i
,这种函数我们一般叫闭包。闭包对捕获的外部变量并不是传值方式访问,而是以引用的方式访问。在for
迭代语句中,每个defer
语句延迟执行的函数引用的都是同一个i
迭代变量,在循环结束后这个变量的值为3,因此最终输出的都是3。
values := []string{"a", "b", "c"}
for _, val := range values {
func() {
fmt.Println(val)
}()
}
// Output:
//a
//b
//c
因为是闭包函数,且立即执行了闭包函数,所以输出正常。
values := []string{"a", "b", "c"}
for _, val := range values {
go func() {
fmt.Println(val)
}()
}
// Output:
//c
//c
//c
因为执行闭包函数使用了goroutine执行,无顺序保证; 各goroutine创建后,执行到fmt.Println语句时,val值已是最后一个val=c。如果goroutine里执行的是资源关闭操作,那会造成资源泄漏;如果执行的是收费结算逻辑,那会造成收费重复去重后亏损。
解决方法:
values := []string{"a", "b", "c"}
for _, val := range values {
v := val
go func() {
fmt.Println(v)
}()
}
values := []string{"a", "b", "c"}
for _, val := range values {
go func(v string) {
fmt.Println(v)
}(val)
}
第一种方法是在循环体内部再定义一个局部变量,这样每次迭代语句的闭包函数捕获的都是不同的变量,这些变量的值对应迭代时的值。第二种方式是将迭代变量通过闭包函数的参数传入,语句会马上对调用参数求值。两种方式都是可以工作的。