捣鼓一个协程库
Last updated
Last updated
今年准备安安心心写一个协程库。一方面是觉得协程挺有意思,另一方面也是因为C/C++在这方面没有一个非常权威的解决方案。 按照我自己风格还是喜欢C++,所以协程库定名为 libcopp 。 源码托管在 github: https://github.com/owt5008137/libcopp 镜像托管 http://git.oschina.net/owent/distinctionpp
协程(coroutine),是一种用户态模拟线程的组件。(具体说明参见:http://zh.wikipedia.org/wiki/%E5%8D%8F%E7%A8%8B)
简单地说,他和线程很像,但是需要用户手动进行执行环境切换,并且是把串行的。详细的解释可以参照一下Boost的文档 http://www.boost.org/doc/libs/1_55_0/libs/coroutine/doc/html/coroutine/intro.html 借用boost的图
简单地说就是在需要的时候将一个执行环境切换到另一个。这些执行环境包含调用栈,寄存器等等。 这么做有什么好处呢?在现在的服务器编程中会有很多的异步操作。当遇到一个异步操作时,要么使用多线程的模型,进行wait操作;要么把所有的异步操作分成很多的同步操作块,异步操作时记录下状态和异步数据,等操作完成时进入下一个操作。简而言之就是状态机。
第一种方法必然会产生很多线程,在线程数过多时,很多CPU都会浪费在线程调度上,浪费在频繁在内核态和用户态的转换上,浪费在锁上。 而第二种方法很容易增加系统复杂度,逻辑复杂时,状态机也会非常复杂,而且记录异步数据也是一件繁琐的事。
而使用协程就能很好地解决这一问题,在有异步操作时切换执行环境到外层。在异步操作完成时切回来。这样异步数据仍然在堆栈中,由于是手动切换,也没有了频繁的wait操作和内核与用户态地转换。但是也有一个局限性就是要手动完成切换操作。
很多托管语言都支持协程,比如python、lua、javascrpt 1.7、elang、go等等,协程切换的时候只要变更托管的执行环境即可。 有些本地语言在内存管理上进行了一些抽象以后也可以支持协程。但是像C/C++这样的语言,使用的时传统的内存管理方式,语言层面很难实现了,所以要借助工具。 在unix环境中有一些函数比如longjmp啊一类的函数可以用来模拟协程。后来glibc中有swapcontext和makecontext系列的函数可以用来转移执行环境。 在windows环境中vc提供了fiber库来提供类似的功能。 其实原理都一样,无非是切换栈的内存位置,保存并修改CPU寄存器内容,最后只要ip寄存器一改,指令的地址就跳到了新的地方。
总得来说,要实现协程主要由两方面的内存管理。一是栈,二是寄存器数据。再来就是一些管理逻辑,比如如何切换调用栈,如何保存寄存器数据,保存哪些寄存器数据等等。
接下来是我们的libcopp的设计思路。首先当然是
跨平台(这是必须的)
支持多种并可自定义栈内存分配方式和寄存器数据保存方式
高效
部分功能提供线程安全实现
无锁
易拓展(容易定制执行环境)
某些环境下支持自动栈拓展
首先是跨平台方面,因为不同平台的寄存器和栈的操作方式不同,这里有很多的hardcode,重新研究也没有必要,就直接扒了boost的fcontext的源码。 这是一份汇编代码集,包含了基本的执行环境初始化和执行环境跳转。同时这份指令集和合理的分配方式可以保证无锁和高效。
在易于拓展方面,我最初想使用c++11的function来控制回调对象,但是这么做一方面会对接口做出一定程度的限制,另一方面由于目前各个平台对c++11的支持各有不同。而且在研究过function的实现原理(详见: 《std和boost的function与bind实现剖析》)后发现,其实std::function就是模拟了多态特性,甚至在VC里,std::function直接就是通过多态实现的。那么这里最终还是通过兼容性最好的多态实现。并把协程环境维护的对象和执行环境对象区分开来。
在协程的栈维护中其实有一个难点,就是如果分配的栈过大,协程数过多时,内存消耗十分庞大(windows默认栈2MB,1000个协程就2GB了)。但是如果分配的太小,调用栈深的时候容易栈溢出。gcc在linux环境下支持一个有意思的特性叫split segment stack,它可以实现栈不够时自动增长拼接,所以这里我也打算实现这个功能。
目前实现的设计图如下:
这是目前实现中的协程对象管理。文档还比较缺失,大致框架已经完成,并且实现了几种常规的allocator和简单的测试(顺便当sample用)。 coroutine container 设计为协程容器,包含了协程操作的一系列资源 coroutinue abstract 为协程操作抽象,实现了一系列类似start、yield、resume等操作(coroutine base)和执行入口(coroutine runnable)。 stack environment 维护了栈的分配规则,包括栈的数据对象(stack context)和栈的分配器(stack allocator) runtime environment 里的是运行环境相关的功能,包含了fcontext对象和调用栈及寄存器切换的功能函数,这部分平台差异较大,代码摘自boost
达到这个状态是不够的,在真正的应用中还会需要能够集中管理。所以接下来的任务很简单
根据多种需求优化结构
实现集中管理器
通用id规则
可自定义id生成规则
可自定义key-value型管理规则
事件通知支持(start、stop、finish、yield、resume等等)
后续想到这么需求了再依次添加
除此之外,还有一些逻辑功能上的规划 比如以后实现一个协程任务系统,支持超时管理,支持线程池(多线程并行执行一些协程),类似.net的await操作等等,具体的可以以后再考虑。
暂时规划就这么多,希望今年内可以利用业余时间完成。也欢迎有兴趣的童鞋提出宝贵意见 ( ^ _ ^ )
协程对象视图