闭包作为协程运行会发生什么

文章讲述了在Go1.22之前,使用闭包处理并发时可能出现的问题,即闭包共享循环变量导致的输出混乱。通过传递参数或创建新变量的方式解决了这个问题。Go1.22引入的新特性解决了这一bug。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原文链接:what happen with closures running as goroutine

由于循环变量的工作方式,在 Go 1.22 版本之前(参见本节末尾的更新),在使用闭包与并发时可能会引起一些混淆。考虑以下程序:

func main() {
    done := make(chan bool)

    values := []string{"a", "b", "c"}
    for _, v := range values {
        go func() {
            fmt.Println(v)
            done <- true
        }()
    }

    // wait for all goroutines to complete before exiting
    for _ = range values {
        <-done
    }
}

你可能错误地期望看到的输出是 a,b,c。但你可能实际看到的是 c,c,c。这是因为循环的每次迭代都使用变量 v 的同一实例,所以每个闭包都共享这个单一变量。当闭包运行时,它打印的是执行 fmt.Println 时的 v 的值,但是 v 可能在启动协程后已经被修改了。为了在问题发生之前帮助检测这个和其他问题,运行 go vet。

为了在启动每个闭包时将 v 的当前值绑定到闭包,必须修改内部循环以在每次迭代时创建一个新变量。一种方法是将变量作为闭包的参数传递:

    for _, v := range values {
        go func(u string) {
            fmt.Println(u)
            done <- true
        }(v)
    }

在这个例子中,v 的值作为参数传递给了匿名函数。然后在函数内部,这个值可以作为变量 u 来访问。

更简单的方法是创建一个新变量,使用一种在 Go 中可能看起来有些奇怪但却完全有效的声明方式:

    for _, v := range values {
        v := v // create a new 'v'.
        go func() {
            fmt.Println(v)
            done <- true
        }()
    }

这种语言的行为,即不为每次迭代定义一个新变量,事后被认为是一个错误,并在 Go 1.22 中得到了解决,Go 1.22 确实为每次迭代创建了一个新变量,从而消除了这个问题。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值