golang的闭包内引用值和range实现

问题描述

一个简单的遍历功能

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)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值