首页 > 其他分享 >Go中的循环变量和闭包

Go中的循环变量和闭包

时间:2023-01-02 20:44:07浏览次数:57  
标签:闭包 变量 val fmt 循环 Go

最近在写goroutine碰到了常见错误

for _, val := range values {
    go func() {
        fmt.Println(val)
    }()
}

在这里,实际结果并不是预期的所有值都输出一遍(而是出现data race),FAQ里给出了两种解决方案:

// 方案1
for _, val := range values {
    go func(val interface{}) {
	fmt.Println(val)
    }(val)
}

// 方案2
for _, val := range values {
    val := val
    go func() {
	fmt.Println(val)
    }()
}

出现上述现象主要涉及以下几个问题:

循环变量

常见的for循环语法for (initStmt; condition; postStmt) block等价于

{
    initStmt;
    while (codition) {
        block;
        postStmt;
    }
}

这里暗含了一个条件,那就是循环变量在迭代过程中是同一个, 这在Go语言中也不例外(For statements)。
这就导致了开头的问题,由于goroutine什么时候执行是不确定的,也就是说这里val的值取决于goroutine执行的时候循环变量val的值。
如果主函数先返回,那么最终结果都是最后一次循环的循环变量。

闭包

闭包主要包含两部分内容,一个函数,以及函数内自由变量的绑定。在开头的例子中,每一个匿名函数

func() {
    fmt.Println(val)
}

都创建了一个闭包,闭包内的val都指向循环变量val

闭包查找自由变量依照作用域向上查找,因此,在方案2里面,自由变量val指向循环内的临时变量val := val, 此时每个goroutine里val的值为每个迭代内的值。

传值调用

Go里面只有传值调用(call by value),也就是每次调用都会复制参数的值,对于指针而言,同样会传一个新的指针,不过指向的内容是一样的。

type A struct {
    a int
}

func f(a *A) {
    fmt.Printf("%p\n", a) // addr1
    fmt.Printf("%p\n", &a) // addr3
}

func main() {
    a := &A{a: 3}
    fmt.Printf("%p\n", a) // addr1
    fmt.Printf("%p\n", &a) // addr2
    f(a)
}

因此, 在方案1里面,每个迭代中val当前的值都被复制到了匿名函数的栈上,此时和循环变量没有任何关系。

range里的循环变量

FAQ里面提到循环中使用同一个变量可能是一个设计错误,这个问题类似的也出现在C#中(foreach),

This behavior of the language, not defining a new variable for each iteration, may have been a mistake in retrospect. It may be addressed in a later version but, for compatibility, cannot change in Go version 1.

C#选择在foreach中复制循环变量,但是在普通的for循环中保留共用变量,后者可能更多的是出于兼容性的保证。不知道后续Go会怎么处理这个“可能”的设计失误。

标签:闭包,变量,val,fmt,循环,Go
From: https://www.cnblogs.com/christophe1997/p/17020484.html

相关文章

  • BBS(仿博客园项目)-基于django框架详解(包含图文)
    BBS项目(仿博客园项目)项目简介使用python中django框架开发类似博客园基本功能的小项目技术:django框架、编程语言(python3.8)、前端基础(bootstarp3.4.1,jQuery3.5.1)、......
  • django的通用视图
    django.views.geneic:django.视图.通用的有时候编写视图是一种很繁重的任务,在django.views中存在的generic 刚好可以解决这个问题。减少工作量,主要的是List 1.首先......
  • Google SEO
    了解robots.txt文件在创建或修改robots.txt文件之前,您应了解这种网址屏蔽方法的限制。根据您的目标和具体情况,您可能需要考虑采用其他机制来确保搜索引擎无法在网络上......
  • xml转golang结构
     直接上代码xmlSrc:=[]byte(`<xml><abc>123</abc></xml>`)varxXMLxml.Unmarshal(xmlSrc,&x)ifx.Abc=="123"{fm......
  • django与python版本对应关系 附加djangorestframework框架
    Django与python版本Django1.11版本兼容Python2.7、3.4、3.5、3.6、3.7(addedin1.11.17)#1.11.5python<=3.6Django2.0版本兼容Python3.4、3.5、3.6、3.7Djang......
  • JavaScript 预解析-变量预解析,函数预解析
    JavaScript预解析目录JavaScript预解析1.预解析2.变量预解析和函数预解析2.1变量预解析(变量提升)2.2函数预解析(函数提升)学习目标预解析变量预解析和函数预解析......
  • JavaScript 作用域-作用域概述,变量作用域,作用域链
    JavaScript作用域-作用域概述,变量作用域,作用域链目录JavaScript作用域-作用域概述,变量作用域,作用域链1.作用域1.1作用域概述1.2全局作用域1.3局部作用域(函数作用......
  • angularJS友好URL实现 good
    nginx部署angularjs时的rewrite问题使用h5+angularjs完成了一个项目此项目在正式环境上使用nginx做webserver此项目的入口在微信/微博分享中由于分享时的项目访问地址中......
  • SQL注入之Oracle,mongoDB等注入
    常见数据库:access、mysql、mssql、MongoDB、postgresql、sqlite、oracle、sybase等Access是没有库之分,比其他数据库低一个等级。目前在市面上的access已经很少了,和asp语言......
  • Django框架:12、Django中间件(了解)、功能插拔式设计、cookie和session
    Django框架目录Django框架一、Django中间件1、自定义中间件2、需要了解的三个方法二、功能的插拔式设计三、cookie与session1、django操作cookie2、django操作session......