Golang中闭包的理解
简介
参考博客:
- https://www.calhoun.io/what-is-a-closure/
- https://blog.cloudflare.com/a-go-gotcha-when-closures-and-goroutines-collide/
Golang的闭包
函数在Golang中是“一等公民”,因此关于函数的特性必须要掌握号,闭包可以看成函数的高阶应用,是Golang高级开发的必备技能。
匿名函数
“一等公民”意味着函数可以像普通的类型(整型、字符串等)一样进行赋值、作为函数的参数传递、作为函数的返回值等。Golang的函数只能返回匿名函数!
代码实例:
var f = func(int) {}func main() {f = func(i int) {fmt.Println(i)}f(2)f = func(i int) {fmt.Println(i * i * i)}f(2)
}
/*
输出:
2
8
*/
上述代码中,f
可以被任何输入一个整型,无返回值的函数给赋值,这类似于C++中的函数指针。因此f
可以看成是一个函数类型的变量。这样,可以动态的改变f
的功能。匿名函数可以动态的创建,与之成对比的常规函数必须在包中编译前就定义完毕。匿名函数可以随时改变功能。
闭包
闭包是匿名函数与匿名函数所引用环境的组合。匿名函数有动态创建的特性,该特性使得匿名函数不用通过参数传递的方式,就可以直接引用外部的变量。这就类似于常规函数直接使用全局变量一样,个人理解为:匿名函数和它引用的变量以及环境,类似常规函数引用全局变量处于一个包的环境。
func main() {n := 0f := func() int {n += 1return n}fmt.Println(f()) // 别忘记括号,不加括号相当于地址fmt.Println(f())
}
/*
输出:
1
2
*/
在上述代码中,
n := 0
f := func() int {n += 1return n
}
就是一个闭包,类比于常规函数+全局变量+包。f
不仅仅是存储了一个函数的返回值,它同时存储了一个闭包的状态。
闭包作为函数返回值
匿名函数作为返回值,不如理解理解为闭包作为函数的返回值,如下代码:
func Increase() func() int {n := 0return func() int {n++return n}
}func main() {in := Increase()fmt.Println(in())fmt.Println(in())
}
/*
输出:
1
2
*/
闭包被返回赋予一个同类型的变量时,同时赋值的是整个闭包的状态,该状态会一直存在外部被赋值的变量in
中,直到in
被销毁,整个闭包也被销毁。
Golang并发中的闭包
Go语言的并发时,一定要处理好循环中的闭包引用的外部变量。如下代码:
func main() {runtime.GOMAXPROCS(runtime.NumCPU())var wg sync.WaitGroupfor i := 0; i < 5; i++ {wg.Add(1)go func() {fmt.Println(i)wg.Done()}()}wg.Wait()
}
输出结果:
5
5
5
5
5
这种现象的原因在于闭包共享外部的变量i
,注意到,每次调用go
就会启动一个goroutine
,这需要一定时间;但是,启动的goroutine
与循环变量递增不是在同一个goroutine
,可以把i
认为处于主goroutine
中。启动一个goroutine
的速度远小于循环执行的速度,所以即使是第一个goroutine
刚起启动时,外层的循环也执行到了最后一步了。由于所有的goroutine
共享i
,而且这个i
会在最后一个使用它的goroutine
结束后被销毁,所以最后的输出结果都是最后一步的i==5
。
我们可以使用循环的延时在验证上述说法:
func main() {runtime.GOMAXPROCS(runtime.NumCPU())var wg sync.WaitGroupfor i := 0; i < 5; i++ {wg.Add(1)go func() {fmt.Println(i)wg.Done()}()time.Sleep(1 * time.Second) // 设置时间延时1秒}wg.Wait()
}
/*
输出结果:
0
1
2
3
4
*/
每一步循环至少间隔一秒,而这一秒的时间足够启动一个goroutine
了,因此这样可以输出正确的结果。
在实际的工程中,不可能进行延时,这样就没有并发的优势,一般采取下面两种方法:
- 共享的环境变量作为函数参数传递:
func main() {runtime.GOMAXPROCS(runtime.NumCPU())var wg sync.WaitGroupfor i := 0; i < 5; i++ {wg.Add(1)go func(i int) {fmt.Println(i)wg.Done()}(i)}wg.Wait()
}
/*
输出:
4
0
3
1
2
*/
输出结果不一定按照顺序,这取决于每个goroutine
的实际情况,但是最后的结果是不变的。可以理解为,函数参数的传递是瞬时的,而且是在一个goroutine
执行之前就完成,所以此时执行的闭包存储了当前i
的状态。
2.使用同名的变量保留当前的状态
func main() {runtime.GOMAXPROCS(runtime.NumCPU())var wg sync.WaitGroupfor i := 0; i < 5; i++ {wg.Add(1)i := i // 注意这里的同名变量覆盖go func() {fmt.Println(i)wg.Done()}()}wg.Wait()
}
/*
输出结果:
4
2
0
3
1
结果顺序原因同1
*/
同名的变量i
作为内部的局部变量,覆盖了原来循环中的i
,此时闭包中的变量不在是共享外循环的i
,而是都有各自的内部同名变量i
,赋值过程发生于循环goroutine
,因此保证了独立。
Golang中闭包的理解相关推荐
- golang的闭包函数理解
golang中在函数中不能声明一个函数,但是可以在函数中声明匿名函数,统称闭包. 最开始接触golang时,对这一块不是特别理解,通过以下代码进行了解: 函数返回是个func()函数,返回的就是闭包函 ...
- 谈谈我对js中闭包的理解
闭包是一个能够访问其他函数作用域的函数. 很显然这样的定义晦涩难懂,很多人都很难理解闭包的真正含义 那么我们就用通俗一点的语言来解析一下什么是闭包 我在知乎上看到一个比较有意思的回答: 由此我们可以通 ...
- golang关于闭包
Golang:"闭包(closure)"到底包了什么? golang 闭包 Golang中闭包的理解 什么是闭包呢?摘用Wikipedia上的一句定义: a closure is ...
- 谈一谈对JS闭包的理解
个人觉得理解闭包,首先要理解以下几个概念. 1.函数的作用域和作用域链 js不像java等其他类语言,它并不存在块级作用域,取而代之的是函数作用域,另一个变量作用域是全局作用域. 函数的作用域:变量在 ...
- javascript中重要概念-闭包-深入理解
在上次的分享中javascript--函数参数与闭包--详解,对闭包的解释不够深入.本人经过一段时间的学习,对闭包的概念又有了新的理解.于是便把学习的过程整理成文章,一是为了加深自己闭包的理解,二是给 ...
- 理解Golang中的nil
参考: 有趣的面试题:Go语言中的nil比较 - 知乎 (zhihu.com) 理解Go语言的nil - 简书 (jianshu.com) Golang中的nil,没有人比我更懂nil! - 知乎 ( ...
- javascript中 (function(){})();如何理解?
javascript中 (function(){})();如何理解? javascript中: (function(){})()是匿名函数,主要利用函数内的变量作用域,避免产生全局变量,影响整体页面环 ...
- JS闭包的理解及常见应用场景
JS闭包的理解及常见应用场景 一.总结 一句话总结: 闭包是指有权访问另一个函数作用域中的变量的函数 1.如何从外部读取函数内部的变量,为什么? 闭包:f2可以读取f1中的变量,只要把f2作为返回值, ...
- Golang中的自动伸缩和自防御设计
Raygun服务由许多活动组件构成,每个组件用于特定的任务.其中一个模块是用Golang编写的,负责对iOS崩溃报告进行处理.简而言之,它接受本机iOS崩溃报告,查找相关的dSYM文件,并生成开发者可 ...
最新文章
- 1085 Perfect Sequence
- python统计文件中的中文字数-Python实现统计文本文件字数的方法
- 日志 查看匹配内容的前后几行
- java nio 应用场景_BIO、NIO、AIO简述及应用场景
- emacs php 配置文件,如何配置emacs进行正确的PHP开发?
- OPPO Reno3系列旗舰官宣:骁龙765G+正反双曲面设计
- 实施和开发哪个前景好_「深圳app开发」app模板开发和app定制开发哪个好呢?
- redis面试常问--缓存穿透
- 【系统分析师之路】第五章 复盘软件工程(开发模型开发方法)
- Tecplot 360 EX 2020 R1中文版
- python培训还是自学
- Linux原生日志系统Rsyslog详解
- 使用burp对Tomcat 弱密码爆破
- excel两列数据对比找不同_比Vlookup好用10倍,它才是Excel函数中的No.1
- python计算勾股定理公式_三角函数、公式、勾股定理、三角形
- 8cm等于多少像素_PPT尺寸你们都设置成多少(我问的不是分辨率像素,而是长、高尺寸)?...
- 基于Pytorch中的Dataset和Dataloader读取Voc类目标检测数据集
- java将小写金额转为大写金额
- 2018年11月11日学习日志
- C# lazy懒加载