协程 or 纤程 ?

转自:
http://blog.csdn.net/blade2001/article/details/10628243

使用协程模型和异步模型的目的都一样,就是在需要等待的时候把CPU让给别的事务,相较于异步,协程的优势在于它将不同事务隔离开,并让程序员可以不用自己管理大量的异步状态,这让程序设计更容易,但这优势只有在异步方式设计中需要涉及到很多不同事务的不同状态时才会显现出来。引入协程、线程、进程,其实是引入多个顺序执行的过程,以协程为例,等待的处理就变成了协程库内部实现的了,在需要等待的地方协程被挂起成waiting状态,协程调度器继续执行别的任务,这时候程序员就不用自己管理状态了,代码一样是按顺序的方式编写。


而服务端需要等待的操作其实并不多,无非是:磁盘IO、数据库、网络,而这些环节又是非逻辑性的代码,只需要初期在机制上设计一次就不需要再动到了,完全可以针对这几个主要环节进行针对性的异步设计,也可以用几个线程把事务隔离开,所以协程的优势其实不明显。


协程的优点:
1. 消耗小, 切换快, 一个进程可以创建成千上万个协程, PK Erlang.

2. 小任务顺序编程很符合人的思维方式, 规避纯异步编程中状态机的复杂性. 使得使用协程写的程序将更加的直观, 逻辑描述方便, 简化编程.纤程用于化异步为同步, 你可以进行一个异步操作以后就切换纤程,等到异步操作完成以后在切换回来,这样,在逻辑上相关的代码就可以写到一个函数里面,而不用人为的分到多个回调函数中。
3. 没有了线程所谓的安全问题, 避免锁机制


协程的缺点:
1. 因为协程的上下文切换依靠的是保存CPU寄存器,自己实现的协程需要格外小心,否则程序很容易出现很难调试的错误
2. setcontext和getcontext函数在i386的CPU架构上是跟LinuxThreads模型不兼容,和linux 2.6以后的NPTL线程模型是兼容的
3. 自己实现的协程不是语言原生支持的,不能像Erlang那样公平调度,也不能像Go语言那样自动扩展栈空间,所以是有一定局限性的。
4. 协程一般只支持所有的协程函数在一个线程里面跑. 无法充分利用多核CPU, 除非把所有的IO和计算操作都剥离成单独的线程.

所以协程适合于状态逻辑比较复杂的事务,  比如一个协议栈的状态机, 或者在线网游里面的任务角色逻辑处理。




实现线程库的一个核心问题是实现线程的切换。线程切换主要做了两件事:一是旧线程执行环境的保存,二是新线程执行环境的恢复。执行环境主要指寄存器和栈空间,栈的切换也是通过寄存器的切换来完成的。
协程是非抢占式用户级线程可以使用两类接口来实现: longjmp和setjmp; swapcontext,makecontext,setcontext.
1 .setjmp用来保存当前的执行环境,longjmp用来还原上次的执行,这样可以实现non-local goto的功能。longjmp和setjmp環境依賴性很高,移植性很低。
2. get/set/makecontext是為操作上下文而提供的POSIX API[5],其中get/makecontext對應著setjmp,而setcontext對應著longjmp。這個API和set/LONGJMP不同,因為能夠指定切換的上下文的入口點和機器堆棧空間,所以可以在新的機器堆棧空間中讓Fiber工作。除此之外,這個API雖然在Windows下沒有被提供,但是POSIX環境下的移植性很高。然而,因為get/setcontext調用了在內部執行系統信號掩碼設定的、被稱為“sigprocmask”的系統調用,和set/longjmp相比,開銷更大。调用getcontext/makecontext,来初始化协程,用swapcontext/setcontext来切换协程。


协程完全运行在用户态,各个协程的切换也只在用户态完成,所以切换开销较小。线程的调度,通常是由操作系统的线程调度器完成,在现代OS中,通常使用抢占式调度策略。而协程的调用,完全依赖于程序员自己,即实现一种合作式调度,只有在主动提出切换时,才会进行切换。
纤程,在其他的语言,比如Python、Lua中都有实现。Python中提供了generate/yield,可以用来简单地模拟协程,而另外一个强大的greenlet库,则是Python中纤程的另一实现。在Lua中,我们可以使用内置的coroutine库。 在Win32中,我们可以使用ConvertThreadToFiber/ConvertFiberToThread,CreateFiber/DeleteFiber来管理协程的创建与销毁, SwitchToFiber来处理Fiber的调度。




现代Unix系统都在ucontext.h中提供用于上下文切换的函数,这些函数有getcontext, setcontext,swapcontext 和makecontext。其中,getcontext用于保存当前上下文,setcontext用于切换上下文,swapcontext会保存当前上下文并切换到另一个上下文,makecontext创建一个新的上下文。实现用户线程的过程是:我们首先调用getcontext获得当前上下文,然后修改ucontext_t指定新的上下文。同样的,我们需要开辟栈空间,但是这次实现的线程库要涉及栈生长的方向。然后我们调用makecontext切换上下文,并指定用户线程中要执行的函数。
在这种实现中还有一个挑战,即一个线程必须可以主动让出CPU给其它线程。swapcontext函数可以完成这个任务
      
利用setcontext函数实现协程的实验:http://1234n.com/?post/4vzsvm
GUN的Pth项目(http://www.gnu.org/software/pth): 利用这setcontext族函数模拟一个与Pthread兼容的用户代码级别线程库。
C语言中著名的协程项目libtask正是利用setcontext族函数来实现协程,当目标系统不支持setcontext族函数时才用汇编模拟。


libtask的运作方式与Go语言对并行操作的模拟方式更接近,几乎是一模一样的设计,因为libtask和Go语言都是跟Plan 9有颜渊的,据我了解Go和libtask提供的Channel作为协程间通讯的方式都源自Plan 9。
ucontext实现的用户级多线程框架2(抢先式多线程): http://www.cnblogs.com/sniperHW/archive/2012/04/02/2429642.html


How to implement fiber: http://blog.csdn.net/seizef/article/details/6567301


并发编程学习总结(好): http://blog.csdn.net/chgaowei/article/details/7237673


协程实现的基础(好): http://tech.uc.cn/?p=1055


云风写的C 的 coroutine 库(完成度很高): http://blog.codingnow.com/2012/07/c_coroutine.html

猜你喜欢

转载自ximeng1234.iteye.com/blog/2292543