闭包是一种可以让你用很是舒服的方式来编程的小技巧,Go也支持闭包。html
假设历来没有接触过闭包,想在一開始就弄懂什么是闭包(closure)是很是困难的,就像递归同样,直到你真正写过、用过它,你才干真正的对它有一个更详细的认识。java
闭包就是一个函数,这个函数包括了执行它所需的上下文环境,这个环境多是几个变量或者也会是其它的(一般就是变量)。说闭包是一个函数不对,更确切地说。闭包是一个打包了其做用域外部的上下文环境的一段执行环境。假设一时间没有理解这段闭包的含义也没关系。这是一个按部就班的过程。编程
那么咱们来看一个网上最通用的解说闭包的样例:闭包
package main import "fmt" func A() func() int { value := 0 return func() int { value++ return value } // A()到这里时按理说应该已经销毁掉了局部变量value } func main() { B := A() fmt.Println(B()) fmt.Println(B()) fmt.Println(B()) }
执行结果为:
并发
1 2 3好好看下这段代码,咱们定义一个函数A(),在A中咱们又定义了还有一个函数B(),而且B将会做为返回值返回给咱们。B的做用就是每次调用都会给A的局部变量value增1。
但是当A函数执行完以后。按理说局部变量value应该已经被销毁了,B没法再对其进行操做,但是从执行结果来看。value却还"活着"。没错,这就是所谓的打包了上下文环境的函数,B就是一个闭包函数,它不只定义了本身的操做流程,而且附带着它的上文环境A中的value变量。编程语言
从这个样例你可能没太看出闭包有什么做用。但是事实上闭包这个东西或多或少会要求编程语言支持匿名函数而且函数在该语言中式第一类型(可以把函数赋值给变量,可以用函数当參数和返回值)。闭包的做用我也不大好说。像JS这种缺陷比較多的语言使用闭包可以带来保护变量的訪问权限、更好的模块化这种优势,而对于Go,我认为闭包最大的优势就是:模块化
1. 不用特地给某个函数取名字了。省事儿~函数
2. 可以把某个暂时使用的函数定义在近期的地方post
3. 和goroutine使用的时候可以直接用go func() {...}() 这种写法。这就不用把每个要并发的函数都在当前执行环境的外部预先定义一遍了。spa
讲完了闭包的基本内容。接下来就讲一讲使用闭包时应该注意的问题了。
原则上讲,闭包用起来的最大优势就是方便,但是不要在不论什么状况下都首先想到使用闭包,因为假设你对闭包缺少了解。那你写出的代码很是可能会有意外的执行效果。来看一下如下的样例:
package main import ( "fmt" ) func main() { done := make(chan bool, 3) for i := 0; i < 3; i++ { go func() { //这里是有问题的。每个routine都打包了外层执行环境中的变量i fmt.Println(i) done <- true }() } for i := 0; i < 3; i++ { <-done } }
执行结果是:
3 3 3假设你细致看下代码的话,应该会认为代码看上去没有不论什么问题。但是结果为甚不是1 2 3呢???事实上Go的官方指南也给出了相关的样例让开发人员们注意闭包和goroutine一块儿使用时要注意的这个问题。
这个样例中事实上第一个循环中的三个goroutine在建立以后都不会立马执行,因为在他们都绑定了外层执行环境中的变量i,因为外层的执行环境随时会更改i的值。因此知道这个循环结束。三个routine都不能開始执行。
这时的解决的方法就是把要用到的外层上下文环境做为參数传递给闭包函数。像这样:
package main import ( "fmt" ) func main() { done := make(chan bool, 3) for i := 0; i < 3; i++ { go func(index int) { // 把外层环境中的i当作參数传进来 fmt.Println(index) done <- true }(i) // 把i传进去 } for i := 0; i < 3; i++ { <-done } }这种话闭包函数就不需要再绑定外层环境中的那个变量i了。这个问题必定要注意,因为Go里会经常将goroutine和闭包配合使用,假设没有处理好上下文打包的问题,就很是可能引起意外的执行效果。