介绍

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变量的值在作为参数传入匿名函数闭包的时候也就同时被锁定。在使用闭包访问外部变量的时候,问问你自己这个变量时候会发生改变,这样的改变对闭包的运行有什么影响。

golang闭包里的坑的更多相关文章

  1. Golang开发者常见的坑

    Golang开发者常见的坑 目录 [−] 初级 开大括号不能放在单独的一行 未使用的变量 未使用的Imports 简式的变量声明仅可以在函数内部使用 使用简式声明重复声明变量 偶然的变量隐藏Accid ...

  2. golang 闭包

    说起golang闭包,在官方手册里面看过一次,没怎么用过,还是因为6哥经常用,阅读他的代码好多闭包,emmm,今天就学习一下. 在过去近十年时间里,面向对象编程大行其道,以至于在大学的教育里,老师也只 ...

  3. Spring里的坑

    Spring里的坑 1. 配置文件配置controller,在Controller中使用注解配置的bean,会出现获取不到bean的情况 步骤一:使用在配置文件 springmvc.xml 中配置的方 ...

  4. Golang闭包的坑

    team leader 发现一个Golang程序的bug,是由不正确使用闭包引起.记载一下,以作备忘. 猜猜一下程序的结果: import ( "fmt" "time&q ...

  5. golang的timer一些坑

    本文代码部分基于dive-to-gosync-workshop的代码 Golang 的NewTimer方法调用后,生成的timer会放入最小堆,一个后台goroutine会扫描这个堆,将到时的time ...

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

    结论: 闭包函数可以直接引用外层代码定义的变量, 但是,注意,闭包函数里面引用的是变量的地址, 当goroutine被调度时,改地址的值才会被传递给goroutine 函数. 介绍 go的闭包是一个很 ...

  7. go的变量redeclare的问题,golang的一个小坑

    go的变量声明有几种方式: 1 通过关键字 var 进行声明 例如:var i int   然后进行赋值操作 i = 5 2 最简单的,通过符号 := 进行声明和赋值 例如: i:=5 golang会 ...

  8. Golang闭包入门了解

    概念闭包就是一个函数与其相关的引用环境组成的一个整体.闭包本质其实是一个函数,但是这个函数会用到函数外的变量,它们共同组成的整体我们叫做闭包. 简单举例说明 package main import ( ...

  9. List的remove方法里的坑

    今天遇到一件怪事,用一个ArrayList添加了一个对象,再调用ArrayList的remove方法删除该对象,当然这时对象是数据库里查出来的,但内容绝对是一样,却发现remove失败了.演示一下,这 ...

随机推荐

  1. ubuntu 命令安装软件

    终端安装(命令安装).第一,找到终端或者按住Ctrl+Alt+t 打开终端;第二输入命令,命令如下:cd /xxx/xxx/,(xxx代表软件包路径,一直到你放置软件包的文件夹),之后输入命令:sud ...

  2. SQL语言类别

    SQL语言主要分为四大类:数据查询语言DQL,数据操纵语言DML, 数据定义语言DDL,数据控制语言DCL. DQL (data query language) DML(data manipulati ...

  3. c:forEach varStatus属性

    转自:https://blog.csdn.net/jzy23682891/article/details/7075904 c:forEach varStatus属性 current当前这次迭代的(集合 ...

  4. 趣味编程:24点(Haskell版)

    24 game/Solve import Data.List import Data.Ratio import Control.Monad data Expr = Constant Rational ...

  5. 前端编程工具WebStorm 10 工具的快捷使用方式

    1.如果是一个空白的文档,要想快速生成HTML的基本结构,可以写一个! 然后按一下tab键,如果是写的一个标签的名字,则会生成基本的标签结构. 2.h1{}:{}中写要显示的文本 3.h1[]:[]中 ...

  6. k8s 问题

    kubelet: Orphaned pod "4db449f0-4eaf-11e8-94ab-90b8d042b91a" found, but volume paths are s ...

  7. Subquery typo with using in(转)

    Subquery typo with using in Do you use the following syntax?   SELECT * FROM TABLE WHERE COLUMN IN ( ...

  8. where T:new() 是什么意思

    经常看到方法后面加where T:new() ,下面来解释下 比如如下这个方法   protected static T CreateNewInstance<T>() where T :  ...

  9. Web标准:一、xhtml css基础知识

    说明:这些知识是我看<十天学会DIV+CSS教程完整版 完美整理+完整代码>这篇文章后记下来的一些内容,包括少部分不懂得地方去百度到的一些解释等,该文章的地址: http://wenku. ...

  10. Android中MD5加密

    最近项目中遇到MD5加密,代码很简单,又是死代码,不过要注意当长度不足32的时候要补个0.下面是具体代码,直接拷贝就能用. public static String getMD5(String str ...