自己动手实现一个简单的协程模型

Python019

自己动手实现一个简单的协程模型,第1张

协程又叫用户级轻量线程,它不需要像线程那样占用大量系统资源,但却能像线程那样并发地运行多个函数,它是怎样实现的呢?让我们先搞清楚它的实现细节,然后再动手自己做一个。

在CPU中有个IP寄存器,它的值决定了下一条将要执行的指令地址,出于安全起见,通常我们不能手动设置它的值,但是有一些指令,如CALL和RET能够间接的改变它。比如CALL指令将函数地址赋值给IP寄存器,使CPU接下来跳到函数中执行,在函数执行完成之后,RET指令从堆栈中弹出CALL之后的那条指令,并且赋值给IP寄存器,那么函数返回后就能按原来的流程继续执行。

还有两个比较重要的寄存器:BP和SP,他们控制着当前函数的局部变量,以及所有的子函数调用。BP指向当前函数的栈顶,在函数运行期间是固定的,SP指向函数的栈底,它们之间的内存区域标识当前函数的执行状态。如下图所示:

如果我们能想到办法,通过某种机制保存函数这时候的状态,是不是可以在多个函数之间切换呢?在c语言库函数中,真有这样的函数:setjmp()和longjmp(),setjmp()负责保存当前函数的执行状态,longjmp()可以在其它函数中随时返回之前的现场,是不是很神奇。看下面的例子:

编译:

执行结果如下:

上面是一个函数的例子,如果有很多函数,我们为每个函数都调用setjmp()保存一个现场,是不是可以随意在多个函数之间切换呢?答案是否定的,因为使用setjmp()和longjmp()有个约束,只能跳一次,而且只能从栈的深处向浅处跳!为什么呢?因为正常的进程栈栈,无论有多少个函数,全部共用一块连续的内存区域,一旦发生跳转,跳转之间的区域全部丢失,这将导致堆栈被破坏,再也跳不回来了!

有朋友会提出问题:可以为每个函数独立设置堆栈吗?答案是可以!但是c函数库的setjmp()和longjmp()不行,我们必须自己动手写。

首先自己实现这两个函数,并保存为setjmp.s:

在setjmp中,只保存了BP和SP这两个寄存器,以及从堆栈中取出函数的返回地址(即原本应该执行的下一条语句!),并且设置返回值为0,加以区别。

在longjmp中,将入参的BP、SP、函数返回地址加以恢复,实际上是通过破坏堆栈的方式欺骗了RET指令!让RET误以为函数正常返回,实际上却切换到了别的函数中。

然后再实现一个简单协程的例子,保存为jmpcall.c:

协程模型流程图示:

编译:

执行结果如下:

OK,实现成功!

不是。异步其实就是多线程。。启用一个线程池中的线程,去执行IO的工作,而主线程则继续向下执行。。。外在的表象,称之为异步,内在的原理,其实是多线程

由于PHP无法操作线程池中的线程,所以也就不存在真正的异步。协程是靠语法层面实现的,本质上其实是个迭代器。仅仅是"看起来像多线程"而已。本质上依然是单线程。

目前主流的WEB后端语言,可以真正操控线程的,其实只有JAVA和C#。。。弱类型语言,全是靠协程来实现的“伪多线程”。在高迸发的情况下,根本不顶用。只能说“总比没有强点”

但是有些WEB框架,可以借助C语言,实现多线程IO,实际效果会比协程好非常多。。。比如Python的Tornado、Twisted、Gevent等框架,JAVASCRIPT的Node.JS框架等,都是借助C语言实现了IO部分的多线程。。虽然比不上JAVA和C#的“源生多线程”,但至少比协程强多了。至于PHP,目前倒是还没听说过这种框架。