golang闭包里的坑

简介:

介绍

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

 

欢迎加群互相学习,共同进步。QQ群:iOS: 58099570 | Android: 572064792 | Nodejs:329118122 做人要厚道,转载请注明出处!
















本文转自张昺华-sky博客园博客,原文链接:http://www.cnblogs.com/sunshine-anycall/p/4771149.html ,如需转载请自行联系原作者

相关文章
|
7月前
|
Go
Go 语言使用 goroutine 运行闭包的“坑”
Go 语言使用 goroutine 运行闭包的“坑”
38 0
|
7月前
|
Serverless Go
Go语言闭包不打烊,让你长见识!
Go语言闭包不打烊,让你长见识!
30 0
|
6月前
|
缓存 Java Serverless
|
6天前
|
Go C++
go 语言回调函数和闭包
go 语言回调函数和闭包
|
6天前
|
存储 编译器 Go
GO闭包实现原理(汇编级讲解)
函数闭包一点也不神秘,它就是函数和引用环境而组合的实体。在Go中,闭包在底层是一个结构体对象,它包含了函数指针与自由变量。Go编译器的逃逸分析机制,会将闭包对象分配至堆中,这样自由变量就不会随着函数栈的销毁而消失,它能依附着闭包实体而一直存在。因此,闭包使用的优缺点是很明显的:闭包能够避免使用全局变量,转而维持自由变量长期存储在内存之中;但是,这种隐式地持有自由变量,在使用不当时,会很容易造成内存浪费与泄露。附着闭包实体而一直存在。
52 0
GO闭包实现原理(汇编级讲解)
|
9月前
|
存储 Rust 算法
Go中的匿名函数与闭包
Go中的匿名函数与闭包
50 0
|
10月前
|
算法 Go 调度
|
10月前
|
Go
golang踩坑 2.goroutine闭包和遍历字符串字符
golang踩坑 2.goroutine闭包和遍历字符串字符
|
JavaScript 前端开发 Java
Go中的闭包、递归
Go中的闭包、递归
46 1
|
安全 Go
Go函数下篇:defer和闭包
Go函数下篇:defer和闭包