【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
【GoLang】golang 闭包 closure 参数传递的蹊跷!的更多相关文章
- python 函数对象(函数式编程 lambda、map、filter、reduce)、闭包(closure)
1.函数对象 作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 秉承着一切皆对象的理念,我们再次回头来看函数(function).函 ...
- Scala 基础(十二):Scala 函数式编程(四)高级(二)参数(类型)推断、闭包(closure)、函数柯里化(curry)、控制抽象
1 参数(类型)推断 参数推断省去类型信息(在某些情况下[需要有应用场景],参数类型是可以推断出来的,如list=(1,2,3) list.map() map中函数参数类型是可以推断的),同时也可以 ...
- JavaScript闭包(Closure)
JavaScript闭包(Closure) 本文收集了多本书里对JavaScript闭包(Closure)的解释,或许会对理解闭包有一定帮助. <你不知道的JavsScript> Java ...
- 聊一下JS中的作用域scope和闭包closure
聊一下JS中的作用域scope和闭包closure scope和closure是javascript中两个非常关键的概念,前者JS用多了还比较好理解,closure就不一样了.我就被这个概念困扰了很久 ...
- 深入理解JavaScript闭包(closure)
最近在网上查阅了不少javascript闭包(closure)相关的资料,写的大多是非常的学术和专业.对于初学者来说别说理解闭包了,就连文字叙述都很难看懂.撰写此文的目的就是用最通俗的文字揭开Java ...
- [转] Java内部类之闭包(closure)与回调(callback)
闭包(closure)是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域.通过这个定义,可以看出内部类是面向对象的闭包,因为它 不仅包含外围类对象(创建内部类的作用域)的信息,还自动拥 ...
- JavaScript 进阶(四)解密闭包closure
闭包(closure)是什么东西 我面试前端基本都会问一个问题"请描述一下闭包".相当多的应聘者的反应都是断断续续的词,“子函数”“父函数”“变量”,支支吾吾的说不清楚.我提示说如 ...
- [转载]学习Javascript闭包(Closure)
学习Javascript闭包(Closure) 源地址: http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures ...
- Swift语言精要-闭包(Closure)
闭包(Closure)这个概念如果没学过Swift的人应该也不会陌生. 学过Javascript的朋友应该知道,在Javascript中我们经常会讨论闭包,很多前端工程师的面试题也会问到什么是闭包. ...
随机推荐
- 提高Axure设计效率的10条建议
http://www.woshipm.com/ucd/92153.html Axure 是创建软件原型的快速有力的工具.上手很容易,但是,其中存在一个危险.这款软件是如此的直观以至于很多用户可以在没有 ...
- centos7.0 安装字体库
最近在centos7.0下用itextpdf将word文档转成pdf时出现字体丢失的情况.网上找了很多资料,各式各样的原因和解决方法.后来经过一番测试发现是centos7.0 minimal没有安装相 ...
- python 环境搭建
python下载地址: 进入https://www.python.org/download/releases/3.3.4/,下载Windows X86-64 MSI Installer (3.3.4) ...
- JS组件系列——图片切换特效:简易抽奖系统
前言:前两天在网上找组件,无意中发现了我们儿时游戏机效果的“SlotMachine组件”,浏览一遍下来,勾起了博主小时候满满的回忆.于是下定决定要研究下这么一个东西,不得不再次叹息开源社区的强大,原来 ...
- 多说评论系统API调用和本地身份说明(JWT)
多说评论系统是一个非常好用的第三方评论插件,聚合了大多数的SNS平台账号登录和分享功能,UI也很不错. 作为网站快速接入评论系统,多说是一个比较好的选择,其也提供了一些实用的API去满足定制化需求. ...
- RapidJSON v1.1.0 发布简介
时隔 15.6 个月,终于发布了一个新版本 v1.1.0. 新版本除了包含了这些日子收集到的无数的小改进及 bug fixes,也有一些新功能.本文尝试从使用者的角度,简单介绍一下这些功能和沿由. P ...
- css的继承性
为了减少css的代码量,很多时候,我们必须要非常重视css的继承性 1.首先,有些属性是不能继承的.这没有任何原因,只是因为它就是这么设置的.举个例子来说:border属性,大家都知道,border属 ...
- C 语言学习 第一次作业总结
第一次的作业是冯老师布置的练习题,需要在pta平台上完成.我这边看不到结果,但是透过冯老师给出的截图,同学们都还是认真的去做的.同时,我这边也布置了一个持续 3 周的作业:熟悉 git 的使用.因为后 ...
- smarty下如何将一个数保存为两位小数
smarty模板是一种缓存技术,下面介绍一下smarty string_format用法 取小数点后2位: 用法如下: //index.php$smarty = new Smarty; $smarty ...
- 红米3 Flyme5.1.9.5插桩适配长期不定时更新
ROM介绍: 2016.9.5更新日志: 1,更新源码到0905 2.修复bug若干. 2016.8.29更新日志: 1,更新源码到0829 2.修复bug若干. 2016.8.22更新日志: 1,更 ...