【GoLang】golang 闭包 closure 参数传递的蹊跷!

结论:

闭包函数可以直接引用外层代码定义的变量,

但是,注意,闭包函数里面引用的是变量的地址,

当goroutine被调度时,改地址的值才会被传递给goroutine 函数。

介绍

go的闭包是一个很有用的东西。但是如果你不了解闭包是如何工作的,那么他也会给你带来一堆的bug。这里我会拿出Go In Action这本书的一部分代码,来说一说在使用闭包的时候可能遇到的坑。全部的代码在github上。

闭包的坑

首先看一段代码:

search/search.go

29  // Launch a goroutine for each feed to find the results.
30  for _, feed := range feeds {
31     // Retrieve a matcher for the search.
32     matcher, exists := matchers[feed.Type]
33     if !exists {
34        matcher = matchers["default"]
35     }
36
37     // Launch the goroutine to perform the search.
38     go func(matcher Matcher, feed *Feed) {
39        Match(matcher, feed, searchTerm, results)
40        waitGroup.Done()
41     }(matcher, feed)
42  }

这段代码从30行开始遍历一个Feed的slice。在for range语句中声明的feed变量的值在每一个循环中都不同。之后从32行的代码在检查一个某个特定的key值是否有值,如果不存在则赋一个默认值。和feed变量一样,matcher的值也是每个循环都不一样。

现在我们可以跳到38行到41行。这几行代码显然还是在for range循环中的。这里我们定义了一个匿名函数,并把这个函数做为一个goroutine运行。这个匿名函数接受两个参数,第一个是Matcher类型的值,第二个是一个Feed类型的指针。在地41行,我们可以蛋刀matcher和feed两个变量被传入了匿名函数中。

这个匿名函数在第39行的实现很有意思。这里我们可以看到一个对Match方法的调用。这个方法接受4个参数,如果你仔细看的话,前两个参数就是我们定义匿名函数声明的而两个参数。后面的两个我们没有在匿名函数中声明。而是作为变量直接在匿名函数使用了。

search/search.go

37     // Launch the goroutine to perform the search.
38     go func(matcher Matcher, feed *Feed) {
39        Match(matcher, feed, searchTerm, results)
40        waitGroup.Done()
41     }(matcher, feed)
42  }

变量searchTerm和results是定义在闭包外部的。我们可以在匿名函数内部直接使用,而不必作为参数传入后再使用。这里就会有一个问题:我们为什么要把变量matcher和feed作为参数传入而其他的两个不是呢?

我在一开始就指出,matcher和feed两个变量的值是如何在每一个for range循环中改变的。searchTerm和results的值不会随着循环而改变,他们的值在每一个goroutine的生命周期中都是常量。当然,这个goroutine就是使用的匿名函数。那么,为什么要这么做呢?

当我们在匿名函数闭包中使用一个变量的时候,我们不必在匿名函数声明的时候作为参数传递。这个匿名函数闭包可以直接访问到定义在其外部的变量,也就是说对这个变量的修改会在匿名函数闭包内部体现出来,也就是这里的goroutine。如果我们把matcher和feed变量这样使用,而不是把他们作为参数传入匿名函数闭包。那么多数情况下gotoutine只会处理for range循环的最后一个值。

在这个例子中,所有的goroutine都会并发执行。for range循环也许在第一个最多第二个goroutine还在运行的时候就运行完了,matcher和feed变量只会有最后一次循环时候的值。也就是说即使不是全部的goroutine也是大部分的goroutine会处理这些变量的相同的值。这种情况适用于searchTerm和results变量,因为他们不会在循环中改变值。

结论

幸好我们可以声明可以接收参数的匿名函数,这些类型的闭包问题也就引刃而解。在我们上面的例子中,当每一个匿名函数都声明在for range的作用域内的时候,matcher和feed变量的值在作为参数传入匿名函数闭包的时候也就同时被锁定。在使用闭包访问外部变量的时候,问问你自己这个变量时候会发生改变,这样的改变对闭包的运行有什么影响。

参考资料:

http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/index.html#closure_for_it_vars

http://studygolang.com/articles/7994

http://studygolang.com/articles/4738

http://blog.csdn.net/htyu_0203_39/article/details/50985187

http://studygolang.com/articles/356

http://www.jb51.net/article/74166.htm

http://www.cnblogs.com/yjf512/archive/2012/12/09/2810313.html

https://www.zhihu.com/question/49341044?from=profile_question_card

http://www.jb51.net/article/81813.htm

时间: 2024-08-26 11:49:52

【GoLang】golang 闭包 closure 参数传递的蹊跷!的相关文章

闭包(closure)与协程共用时要注意的事情

闭包是一种可以让你用非常舒服的方式来编程的小技巧,Go也支持闭包.如果从来没有接触过闭包,想在一开始就弄懂什么是闭包(closure)是非常困难的,就像递归一样,直到你真正写过.用过它,你才能真正的对它有一个更具体的认识. 闭包就是一个函数,这个函数包含了运行它所需的上下文环境,这个环境可能是几个变量或者也会是其他的(通常就是变量).说闭包是一个函数不正确,更确切地说,闭包是一个打包了其作用域外部的上下文环境的一段运行环境.如果一时间没有理解这段闭包的含义也不要紧,这是一个循序渐进的过程. 那么

php中的匿名函数和闭包(closure)

一:匿名函数 (在php5.3.0 或以上才能使用) php中的匿名函数(Anonymous functions), 也叫闭包函数(closures), 允许指定一个没有名称的函数.最常用的就是回调函数的参数值.(http://php.net/manual/zh/functions.anonymous.php) 匿名函数的定义: $closureFunc = function(){ .... }; eg: 把匿名函数赋值给变量,通过变量来调用 $closureFunc = function($s

javascript_function(闭包Closure)

javascript闭包(Closure) 所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分. 上面是官方的解释,但这解释只会让人头晕.要理解闭包,首先理解两点:变量的作用域以及作用域链,这两个在前面都已经介绍过了,并且举了简单了列子,来回顾一下: var color = "blue"; function changeColor(){ var anotherColor = "red"; funct

javascript 闭包(closure)

<script type="text/javascript">    //闭包(closure):内层函数可以引用存在于包围它的函数内的变量,即使外层函数的执行已经结束    //注意内层函数引用的外层函数内的变量是外层函数执行结束后的最终值    test(1);    function test(a) { //外层函数        alert(a+' 外层函数开始执行');        setTimeout(function(){//内层函数           

Swift语言精要-闭包(Closure)

闭包(Closure)这个概念如果没学过Swift的人应该也不会陌生. 学过Javascript的朋友应该知道,在Javascript中我们经常会讨论闭包,很多前端工程师的面试题也会问到什么是闭包. 那么,什么是闭包呢? 让我们看下在Javascript中闭包的解释: Closures are functions that have access to variables from another function’s scope. (This is often accomplished by

Swift中使用typealias定义一个闭包closure

在OC中我们定义一个Blocks是这样定义的: typedef void (^ZWProgressHUDCompletionBlock)(); 在Swift中定义一个闭包是这样的: typealias ZWProgressHUDCompletionBlock=()->Void 转载请注明!!!欢迎大家加入交流群:爱疯.爱Coding:209476515 Swift中使用typealias定义一个闭包closure,布布扣,bubuko.com

深入理解JavaScript闭包(closure)

最近在网上查阅了不少Javascript闭包(closure)相关的资料,写的大多是非常的学术和专业.对于初学者来说别说理解闭包了,就连文字叙述都很难看懂.撰写此文的目的就是用最通俗的文字揭开Javascript闭包的真实面目. 一.什么是闭包? “官方”的解释是:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分.相信很少有人能直接看懂这句话,因为他描述的太学术.其实这句话通俗的来说就是:JavaScript中所有的function都是一个

Python闭包Closure

Python的闭包和Python的内部函数 1 Python内部函数 def out(x): def inner(y): return "inner use % s" % y return inner(x) print out("jeapedu") 在out里定义了一个inner函数,out的返回值是调用inner(x)的值 2 python的闭包Closure def closure(x): def inner(y): return "closure u

[转] Java内部类之闭包(closure)与回调(callback)

闭包(closure)是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域.通过这个定义,可以看出内部类是面向对象的闭包,因为它 不仅包含外围类对象(创建内部类的作用域)的信息,还自动拥有一个指向此外围类对象的引用,在此作用城内,内部类有权操作所有的成员,包括private 成员. Java最引人争议的问题之一就是,人们认为Java应该包含某种类似指针的机制,以允许回调(callback).通过回调,对象能够携带一些信息,这些信息允许它在稍后的某个时刻调用初始的对象. 稍后将会看到