问题描述
一个简单的遍历功能
func demo0(){
// 顺序遍历, i,a 地址不变, 值变化
arr := []string{"dog", "cat", "mouse"}
for i, a := range arr{
fmt.Printf("func %d get %s\n", i, a)
}
}
输出:
func 0 get dog
func 1 get cat
func 2 get mouse
然后想要通过goroutine来完成并发
func demo2(){
arr := []string{"dog", "cat", "mouse"}
wg := &sync.WaitGroup{}
for i, a := range arr{
wg.Add(1)
go func(){
fmt.Printf("func %d get %s\n", i, a)
wg.Done()
}()
}
wg.Wait()
}
但是得到输出:
func 2 get mouse
func 2 get mouse
func 2 get mouse
仅得到数组的最后一个值
原因分析
这里涉及到两个问题: range的实现和go闭包引用外部值的形式
range的实现
func demo0(){
// 顺序遍历, i,a 地址不变, 值变化
arr := []string{"dog", "cat", "mouse"}
for i, a := range arr{
fmt.Printf("func %d(%v) get %s(%v)\n", i, &i, a, &a)
}
}
把i和a的地址打印出来就会发现i和a的地址都没有改变, range操作相当于
var a string
for i := 0; i < len(arr); i++{
a = arr[i]
fmt.Printf("func %d(%v) get %s(%v)\n", i, &i, a, &a)
}
go闭包引用外部值的形式
func()内部的i和a, 通过地址值引用, 又在运行时才根据地址值找到值是多少, 因此都指向了arr数组的最后一个值.
下面的例子更加明显
func demo3(){
// 闭包 通过地址值引用外部变量 地址不变 值都相同
arr := []string{"dog", "cat", "mouse"}
funcArr := make([]func(), 0) // 函数数组
for i, a := range arr{
goFunc := func(){
fmt.Printf("func %d(%v) get %s(%v)\n", i, &i, a, &a)
}
funcArr = append(funcArr, goFunc)
}
for _, f := range funcArr{
f()
}
}
输出
func 2(0xc00008c020) get mouse(0xc00008e030)
func 2(0xc00008c020) get mouse(0xc00008e030)
func 2(0xc00008c020) get mouse(0xc00008e030)
f()在被调用时才确定闭包内使用到的外部值
解决方法
解决方法也很简单, 使用参数传值, 而不是引用外部值即可
func demo2(){
arr := []string{"dog", "cat", "mouse"}
wg := &sync.WaitGroup{}
for i, a := range arr{
wg.Add(1)
go func(i int, a string){
fmt.Printf("func %d(%v) get %s(%v)\n", i, &i, a, &a)
wg.Done()
}(i, a)
}
wg.Wait()
}
或者在for内部使用一个新变量也可以解决
func demo2(){
arr := []string{"dog", "cat", "mouse"}
wg := &sync.WaitGroup{}
for i, a := range arr{
wg.Add(1)
index, val := i, a // 新的地址值
go func(){
fmt.Printf("func %d(%v) get %s(%v)\n", index, &index, val, &val)
wg.Done()
}()
}
wg.Wait()
}
输出
func 2(0xc00001c0e0) get mouse(0xc000010240)
func 1(0xc00001c0f0) get cat(0xc000010260)
func 0(0xc00008c000) get dog(0xc00008e000)