函数闭包对于大多数读者而言并不是什么高级词汇,那什么是闭包呢?这里摘自Wiki上的定义:

a closure is a record storing a function together with an environment.The environment is a mapping associating each free variable of the function (variables that are used locally, but defined in an enclosing scope) with the value or reference to which the name was bound when the closure was created.

简而言之,闭包是一个由函数和引用环境而组合的实体。闭包在实现过程中,往往是通过调用外部函数返回其内部函数来实现的。在这其中,引用环境是指的外部函数中自由变量(内部函数使用,但是却定义于外部函数中)的映射。内部函数通过引入外部的自由变量,使得这些变量即使离开了外部函数的环境也不会被释放或删除,在返回的内部函数仍然持有这些信息。

这段话可能不太好理解,我们直接用看例子。

可以看到,Go中的两条特性(函数是一等公民,支持匿名函数)使其很容易实现闭包。

closurexouterxouterxclosurex
x

实现原理

我们不妨从汇编入手,将上述代码稍微修改一下

得到编译后的汇编语句如下。

x:=1runtime.newobjectnew
x:=100runtime.newobject

留着疑问,继续往下看。我们发现有以下语句

type.noalg.struct { F uintptr; "".x *int }(SB)
runtime.newobjectLEAQ xxx yyyxxxyyyouterx

我们可以通过逃逸分析来验证我们的结论

x:=1runtime.newobject
x++
x:=1runtime.newobjectxxint

这其实就是Go编译器做得精妙的地方:当闭包内没有对外部变量造成修改时,Go 编译器会将自由变量的引用传递优化为直接值传递,避免变量逃逸。

总结

函数闭包一点也不神秘,它就是函数和引用环境而组合的实体。在Go中,闭包在底层是一个结构体对象,它包含了函数指针与自由变量。

Go编译器的逃逸分析机制,会将闭包对象分配至堆中,这样自由变量就不会随着函数栈的销毁而消失,它能依附着闭包实体而一直存在。因此,闭包使用的优缺点是很明显的:闭包能够避免使用全局变量,转而维持自由变量长期存储在内存之中;但是,这种隐式地持有自由变量,在使用不当时,会很容易造成内存浪费与泄露。

在实际的项目中,闭包的使用场景并不多。当然,如果你的代码中写了闭包,例如你写的某回调函数形成了闭包,那就需要谨慎一些,否则内存的使用问题也许会给你带来麻烦。

转自:Golang技术分享

Golang资料分享:Go学习资料合集