简介

参考博客:

  • 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了,因此这样可以输出正确的结果。

在实际的工程中,不可能进行延时,这样就没有并发的优势,一般采取下面两种方法:

  1. 共享的环境变量作为函数参数传递:
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中闭包的理解相关推荐

  1. golang的闭包函数理解

    golang中在函数中不能声明一个函数,但是可以在函数中声明匿名函数,统称闭包. 最开始接触golang时,对这一块不是特别理解,通过以下代码进行了解: 函数返回是个func()函数,返回的就是闭包函 ...

  2. 谈谈我对js中闭包的理解

    闭包是一个能够访问其他函数作用域的函数. 很显然这样的定义晦涩难懂,很多人都很难理解闭包的真正含义 那么我们就用通俗一点的语言来解析一下什么是闭包 我在知乎上看到一个比较有意思的回答: 由此我们可以通 ...

  3. golang关于闭包

    Golang:"闭包(closure)"到底包了什么? golang 闭包 Golang中闭包的理解 什么是闭包呢?摘用Wikipedia上的一句定义: a closure is ...

  4. 谈一谈对JS闭包的理解

    个人觉得理解闭包,首先要理解以下几个概念. 1.函数的作用域和作用域链 js不像java等其他类语言,它并不存在块级作用域,取而代之的是函数作用域,另一个变量作用域是全局作用域. 函数的作用域:变量在 ...

  5. javascript中重要概念-闭包-深入理解

    在上次的分享中javascript--函数参数与闭包--详解,对闭包的解释不够深入.本人经过一段时间的学习,对闭包的概念又有了新的理解.于是便把学习的过程整理成文章,一是为了加深自己闭包的理解,二是给 ...

  6. 理解Golang中的nil

    参考: 有趣的面试题:Go语言中的nil比较 - 知乎 (zhihu.com) 理解Go语言的nil - 简书 (jianshu.com) Golang中的nil,没有人比我更懂nil! - 知乎 ( ...

  7. javascript中 (function(){})();如何理解?

    javascript中 (function(){})();如何理解? javascript中: (function(){})()是匿名函数,主要利用函数内的变量作用域,避免产生全局变量,影响整体页面环 ...

  8. JS闭包的理解及常见应用场景

    JS闭包的理解及常见应用场景 一.总结 一句话总结: 闭包是指有权访问另一个函数作用域中的变量的函数 1.如何从外部读取函数内部的变量,为什么? 闭包:f2可以读取f1中的变量,只要把f2作为返回值, ...

  9. Golang中的自动伸缩和自防御设计

    Raygun服务由许多活动组件构成,每个组件用于特定的任务.其中一个模块是用Golang编写的,负责对iOS崩溃报告进行处理.简而言之,它接受本机iOS崩溃报告,查找相关的dSYM文件,并生成开发者可 ...

最新文章

  1. 1085 Perfect Sequence
  2. python统计文件中的中文字数-Python实现统计文本文件字数的方法
  3. 日志 查看匹配内容的前后几行
  4. java nio 应用场景_BIO、NIO、AIO简述及应用场景
  5. emacs php 配置文件,如何配置emacs进行正确的PHP开发?
  6. OPPO Reno3系列旗舰官宣:骁龙765G+正反双曲面设计
  7. 实施和开发哪个前景好_「深圳app开发」app模板开发和app定制开发哪个好呢?
  8. redis面试常问--缓存穿透
  9. 【系统分析师之路】第五章 复盘软件工程(开发模型开发方法)
  10. Tecplot 360 EX 2020 R1中文版
  11. python培训还是自学
  12. Linux原生日志系统Rsyslog详解
  13. 使用burp对Tomcat 弱密码爆破
  14. excel两列数据对比找不同_比Vlookup好用10倍,它才是Excel函数中的No.1
  15. python计算勾股定理公式_三角函数、公式、勾股定理、三角形
  16. 8cm等于多少像素_PPT尺寸你们都设置成多少(我问的不是分辨率像素,而是长、高尺寸)?...
  17. 基于Pytorch中的Dataset和Dataloader读取Voc类目标检测数据集
  18. java将小写金额转为大写金额
  19. 2018年11月11日学习日志
  20. C# lazy懒加载

热门文章

  1. 高阻态是0还是1_羽毛球拍穿线,先拉横线还是竖线?是否横线要比竖线高1到2磅?...
  2. python每日一题公众号_python每日一题总结4
  3. 【论文笔记】Neural Graph Collaborative Filtering
  4. Python函数的静态变量
  5. Altium AD20更改原理图背景颜色
  6. 【oracle】常用函数总结
  7. 【Angular 4】响应式编程
  8. 程序员,你会说话吗?
  9. MySQL 5.7 忘记密码
  10. 有关 this 指向问题总结