我用了这么多课程,讲了那么多NIO的相关的知识,终于来到了协程这一节课。
先声明一点,本节课看起来觉得看不懂的同学,不要着急,可以先放下,等慢慢理解了进程,线程的概念以后,再回来看这节课。
Callback Hell
Callback模型看起来,已经很不错了,但是,如果要连续地读取多个数值,那么多层的Callback就会嵌套在一起。这个嵌套会使得代码很难看,也很难维护。代码最好是能像客户端那样顺序地写。所有IO模型中,阻塞式的,顺序式的,同步代码是最直观,最简单的。
那我们能不能把这个callback修改成同步的代码呢?
答案是,能,这个方法就是协程。
协程的定义协程是个什么东西呢?它是一种轻量级的,用户态的执行单元。它占用的内存非常少,几乎是需要多少才用多少。相比起线程在创建之初就指定栈空间,协程所使用的内存可以动态地变化。它主要有两个特点:
占用的资源更少。
所有的切换和调度都发生在用户态。
协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。在并发编程中,协程与线程类似,每个协程表示一个执行单元,有自己的本地数据,与其它协程共享全局数据和其它资源。目前主流语言基本上都选择了多线程作为并发设施,与线程相关的概念是抢占式多任务(Preemptive multitasking),而与协程相关的是协作式多任务。
不管是进程还是线程,每次阻塞、切换都需要陷入系统调用(system call),先让CPU跑操作系统的调度程序,然后再由调度程序决定该跑哪一个进程(线程)。而且由于抢占式调度执行顺序无法确定的特点,使用线程时需要非常小心地处理同步问题,而协程完全不存在这个问题(事件驱动和异步程序也有同样的优点)。
协作式的任务,是要用户自己来负责任务的让出的。如果一个任务不主动让出,其他任务就不会得到调度。这是协程的一个弱点,但是好好的规划,这其实是一个可以变得很强大的优点。
关于协程,有一篇很好的文章:C/C++协程库libco:微信怎样漂亮地完成异步化改造
这是微信服务端的协程化改造,这里面也有很多关于协程的介绍。大家可以好好读一读。
其他语言中的协程
说了这么多,可能大家还是没有一个直观的感受。我还是举例来说明,从最简单的到复杂的,一点点来。
很多编程语言中都有协程。Lua, Ruby 等等都有自己的协程实现。Go完全就是因为协程而发展壮大的。协程的实现有很多方式,我可以从最简单的stackless的协程开始一点点为大家示例。
最简单的例子,我先用python举个例子:
def fib():
a = 0
b = 1
while True:
yield b
a, b = b, a + b
o = fib()
for i in xrange(10):
print o.next()
Python中使用的是一种名为Generator的东西。大家可以把这些代码保存到一个名为test.py中,然后使用python来执行它:
可以看到,这个程序可以打印出Fibonacci的前10项,实际上,只要你愿意,这个o.next()方法是可以一直执行下去的,可以打印出Fibonacci的任意项数。这个程序的奥秘就在于yield方法。
在Python中,如果函数定义中使用了yield语句,这个函数就会变成一个叫做Generator的东西,调用穿上函数时,返回的那个对象就是Generator,例如我们例子中的o。在Generator上调用next方法,就会开始真正执行fib中的逻辑。分别是把a设为0,b设为1,然后进入一个无限循环。在循环体中,遇到yield,函数调用就会退出,并且把 b 做为返回值返回到 next 的调用者那里。但是这个返回并不是真的就把函数全部退掉了,而是保存函数现场。这一步非常重要,这保证了下一次再调next 的时候,a, b 的值会是上次中断时a 和 b的值。
a, b 的值都是保存在 o 中的,o 就像一个可以源源不断地产生数字的发生器一样,所以给它一个名字叫Generator
理解了这个函数,大家对于协程保存现场可能会有一些初步的印象了。由于yield这种东西,只能保存一层函数调用,所以这种东西也叫 stackless coroutine。就是说整个函数调用栈,这里是无法保存的。
如果你的机器上没有python,那也不用去装,因为你还可以使用chrome来运行javascript的例子。打开chrome浏览器,点F12,然后选择console:
像我这样输入 fib 函数的定义,然后使用Generator来生成fibonacci数列。JS中的生成器与Python是一样的。
好了,今天就先讲这么多,下节课我们讲 Kotlin 和 C# 的实现,大家就能更清楚了。
上一节课: