Golang的for循环的坑——循环变量与闭包

711 阅读1分钟

闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包的常见的方式,就是在一个函数A内部创建另一个函数B,通过另一个函数B访问这个函数A的局部变量。

JavaScript中的for循环的闭包有一个坑,先看代码:

function print(i){
  console.log("index: ", i)
}
var arr = []
for (var index = 0; index < 3; index++) {
  arr[index] = function(){
    print(index)
  }
}
arr.forEach(element => {
  element()
})

结果如下:

index:  3 
index:  3 
index:  3 

如果你的循环变量是用var声明的那么每次调用闭包打印的其实不是index循环中的副本而是index最后的值,也就是3

Go语言声明变量也是用var, 那么是不是也会有类似的问题出现呢?让我们来看下这段代码(这里的例子我们采用简写语法糖没有使用var, 但是结果是一样的)


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

结果不会是0 1 2, 而且可能会出现打印3的情况, IDE也会出现警告 因为这里我们使用的是goroutine,可能有的goroutine会在循环还没结束的时候就运行了,所以和上面js的例子不同, 不一定会是3个goroutine都打印3

正确用法:

// 结果是0 1 2, 有可能是乱序的
for i:=0; i<3; i++ {
	n := i
	go func(){
		fmt.Println(n)
	}()
}

// 或者
for i:=0; i<3; i++ {
	go func(n int){
		fmt.Println(n)
	}(i)
}

总结:

在for循环里如果使用了闭包并且引用了循环变量i会有data race的问题, 而且因为闭包的原因, i在循环结束后不会被释放,会被放在堆内存中, 而且可能会打印出i 最后的值(在本例子中是3)