Golang Go语言中 1.23 版本,for-range 指针数组,闭包捕获的 range 变量为何还是最后一个呢?

发布于 1周前 作者 yibo5220 来自 Go语言
for _, cg := range consumers {
		log.Logger.Infof("%p", cg)
		utils.SafeGo(func() {
			log.Logger.Infof("%#v", cg)
			log.Logger.Infof("%p", cg)
			StartConsumer(ctx, cfg.Endpoint, cfg.AccessKey, cfg.SecretKey, cg)
		})
	}

输出:

0xc000683540
0xc000683560
&queue.Test2{groupName:"test2"}
&queue.Test2{groupName:"test2"}
0xc000683560
0xc000683560

consumers 包含两个元素,值都是指针。 1.22 版本开始 range 每次不是会创建新变量了吗?为什么我 SafeGo 里面 cg 拿到的还是最后一个的值?


Golang Go语言中 1.23 版本,for-range 指针数组,闭包捕获的 range 变量为何还是最后一个呢?

更多关于Golang Go语言中 1.23 版本,for-range 指针数组,闭包捕获的 range 变量为何还是最后一个呢?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html

7 回复

用匿名函数的参数传进去就 ok 了

更多关于Golang Go语言中 1.23 版本,for-range 指针数组,闭包捕获的 range 变量为何还是最后一个呢?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


按道理 1.22 开始版本不需要这样做了呀,我的疑惑就是这里。

cg 变量从头到尾都只有一个啊,所有匿名函数捕获的变量都指向这同一个 cg

检查下 go.mod 里面的 go 的版本,如果是 1.22 之前的话新的特性就不会生效

每次循环 cg 变量都是不同的,你看输出的指针地址。
https://go.dev/play/p/kV2gxXfzVfc 你看看整个测试代码,我帖子里面的代码和这个测试的一样的,就除了 slice 的数据类型不同。

还真是,我一直以为 go.mod 里面那个版本只是用来看我这个项目需要的最低 go 版本,原来还会影响实际运行版本的行为。

在Go语言的1.23版本中(注意,目前最新的Go版本远超过1.23,但原理相同),for-range循环在处理指针数组时,闭包捕获的range变量行为是符合预期的,即闭包捕获的是循环结束时的最后一个变量的值。这是因为range循环中的变量(如iv,假设你在迭代数组arr)在每次迭代时都会被重用,而不是每次迭代都创建新的变量。

当你在for-range循环中创建一个闭包,并试图在闭包中使用range变量时,实际上你是在引用循环结束时该变量的最终状态。这是因为在Go中,闭包捕获的是变量的引用,而不是变量的副本。

为了解决这个问题,你可以通过显式地在每次迭代中创建一个新的变量,并将其传递给闭包。例如:

arr := []*int{new(int), new(int)}
*arr[0] = 1
*arr[1] = 2

for _, ptr := range arr {
    temp := ptr // 创建一个新的变量来持有当前指针
    go func() {
        fmt.Println(*temp) // 使用新的变量
    }()
}

在这个修改后的例子中,temp是一个在每次迭代中都新创建的变量,它持有当前迭代中ptr的值。因此,当闭包执行时,它访问的是正确的temp变量的值,而不是range循环结束时ptr的最终值。

回到顶部