闭包(closure)与协程共用时要注意的事情

闭包是一种可以让你用非常舒服的方式来编程的小技巧,Go也支持闭包。如果从来没有接触过闭包,想在一开始就弄懂什么是闭包(closure)是非常困难的,就像递归一样,直到你真正写过、用过它,你才能真正的对它有一个更具体的认识。

闭包就是一个函数,这个函数包含了运行它所需的上下文环境,这个环境可能是几个变量或者也会是其他的(通常就是变量)。说闭包是一个函数不正确,更确切地说,闭包是一个打包了其作用域外部的上下文环境的一段运行环境。如果一时间没有理解这段闭包的含义也不要紧,这是一个循序渐进的过程。

那么我们来看一个网上最通用的讲解闭包的例子:

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. 可以把某个临时使用的函数定义在最近的地方

3. 和goroutine使用的时候可以直接用go func() {...}() 这样的写法,这就不用把每个要并发的函数都在当前运行环境的外部预先定义一遍了。

讲完了闭包的基本内容,接下来就讲一讲使用闭包时应该注意的问题了。原则上讲,闭包用起来的最大优点就是方便,但是不要在任何情况下都首先想到使用闭包,因为如果你对闭包缺乏了解,那你写出的代码很可能会有意外的运行效果。来看一下下面的例子:

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和闭包配合使用,如果没有处理好上下文打包的问题,就很可能引发意外的运行效果。

如果转载,请注明出处:http://blog.csdn.net/gophers

时间: 2024-08-17 18:25:04

闭包(closure)与协程共用时要注意的事情的相关文章

关于协程:nodejs和golang协程的不同

nodejs和golang都是支持协程的,从表现上来看,nodejs对于协程的支持在于async/await,golang对协程的支持在于goroutine.关于协程的话题,简单来说,可以看作是非抢占式的轻量级线程. 协程本身 一句话概括,上面提到了 "可以看作是非抢占式的轻量级线程". 在多线程中,把一段代码放在一个线程中执行,cpu会自动将代码分成碎片,并在一定时间切换cpu控制权,线程通过锁机制确保自己使用的资源在cpu执行别的线程的代码时被修改(占用的内存堆栈.硬盘数据资源等)

Lua的协程和协程库详解

我们首先介绍一下什么是协程.然后详细介绍一下coroutine库,然后介绍一下协程的简单用法,最后介绍一下协程的复杂用法. 一.协程是什么? (1)线程 首先复习一下多线程.我们都知道线程——Thread.每一个线程都代表一个执行序列. 当我们在程序中创建多线程的时候,看起来,同一时刻多个线程是同时执行的,不过实质上多个线程是并发的,因为只有一个CPU,所以实质上同一个时刻只有一个线程在执行. 在一个时间片内执行哪个线程是不确定的,我们可以控制线程的优先级,不过真正的线程调度由CPU的调度决定.

在PHP中使用协程实现多任务调度

PHP5.5一个比较好的新功能是加入了对迭代生成器和协程的支持.对于生成器,PHP的文档和各种其他的博客文章已经有了非常详细的讲解.协程相对受到的关注就少了,因为协程虽然有很强大的功能但相对比较复杂, 也比较难被理解,解释起来也比较困难. 这篇文章将尝试通过介绍如何使用协程来实施任务调度, 来解释在PHP中的协程. 我将在前三节做一个简单的背景介绍.如果你已经有了比较好的基础,可以直接跳到“协同多任务处理”一节. 迭代生成器 生成器也是一个函数,不同的是这个函数的返回值是依次输出,而不是只返回一

从Erlang进程看协程思想

从Erlang进程看协程思想 多核慢慢火了以后,协程类编程也开始越来越火了.比较有代表性的有Go的goroutine.Erlang的Erlang进程.Scala的actor.windows下的fibre(纤程)等,一些动态语言像Python.Ruby.Lua也慢慢支持协程. 其实我们听过协程相关很多名词,下面大致来解释一下: OS进程: 进程是资源管理的最小单元,包括进程控制块(PCB).程序段.数据段 OS线程: 线程是程序执行的最小单元,由线程ID,当前指令指针(PC),寄存器集合和堆栈组成

python中的协程(协同程序)

协程:将函数编写为一个能处理输入参数的任务 使用yield语句并以表达式yield的形式创建协程 #匹配器案例: def print_info(data):    print('Looking for',data);    while True:      line = (yield)      if data in line:        print(line); 上面这个函数 就是一个协程程序 要使用这个函数 首先需用调用它 并且 向前执行到第一条yield语句 info = print_

[转]-Lua协程的实现

协程是个很好的东西,它能做的事情与线程相似,区别在于:协程是使用者可控的,有API给使用者来暂停和继续执行,而线程由操作系统内核控制:另 外,协程也更加轻量级.这样,在遇到某些可能阻塞的操作时,可以使用暂停协程让出CPU:而当条件满足时,可以继续执行这个协程.目前在网络服务器领域, 使用Lua协程最好的范例就是ngx_lua了,我自己的项目qnode也是借助Lua协程的概念:每一个qnode中的微进程底层对应一个Lua协程, 这样底层的异步操作可以在使用者使用同步的方式写出来.Coool. 来看

PHP 协程:Go + Chan + Defer

Swoole4为PHP语言提供了强大的CSP协程编程模式.底层提供了3个关键词,可以方便地实现各类功能. Swoole4提供的PHP协程语法借鉴自Golang,在此向GO开发组致敬 PHP+Swoole协程可以与Golang很好地互补.Golang:静态语言,严谨强大性能好,PHP+Swoole:动态语言,灵活简单易用 本文基于Swoole-4.2.9和PHP-7.2.9版本 关键词 go :创建一个协程 chan :创建一个通道 defer :延迟任务,在协程退出时执行,先进后出 这3个功能底

异步回调,事件,线程池与协程

在发起一个异步任务时,指定一个函数任务完成后调用函数 为什么需要异步 在使用线程池或进程池提交任务时想要任务的结果然后将结果处理,调用shudown 或者result会阻塞 影响效率,这样的话采用异步调用 比如result本来是用水壶烧水烧开了拿走,烧下一个 用shutdown可以将水壶一起烧但是一个一个拿走 call_done_back是一起烧,每个好了会叫你拿走做其他事 . 1.使用进程池时,回调函数都是主进程中执行执行 2. 使用线程池时,回调函数的执行线程是不确定的,哪个线程空闲就交给哪

python yield、yield from与协程

从生成器到协程 协程是指一个过程,这个过程与调用方协作,产出由调用方提供的值.生成器的调用方可以使用 .send(...)方法发送数据,发送的数据会成为yield表达式的值.因此,生成器可以作为协程使用. 从句法上看,生成器与协程都是包含yield关键字的函数.但是,在协程中,yield通常出现在表达式的右边(* = yield *),可以产出值一可以不产出(yield关键字后边没有表达式,产出None). 协程有四个状态: GEN_CREATED:等待开始执行 GEN_RUNNING:正在执行