基于libevent的简单协程实现

协程这个概念如果有碰过erlangr的人应该都不陌生。协程,即协作式程序,其思想是,一系列互相依赖的协程间依次使用CPU,每次只有一个协程工作,而其他协程处于休眠状态。协程在控制离开时暂停执行,当控制再次进入时只能从离开的位置继续执行。

其实基于协程的开发框架并不少,如gevent,libtask等。

说到协程,就不得不提起一些常见的网络编程模型。常见的有同步模型和异步模型。同步模型一般来说是一个连接独占一个线程,apache就是基于这种模型的,好处就是处理请求是同步顺序执行的,编写逻辑比较清晰,带来的坏处也比较明显,当连接越来越多时,线程也越来越多,资源开销等随之更多,不利于高并发请求的处理;异步模型的话一般使用非阻塞IO,配合多路复用机制(epoll、select等)使用。这样的话不需要像同步模型那样一次请求就需要独占一个线程,当需要读写IO时,如果IO数据未准备好,就可以先阻塞在多路复用接口,等到数据到来再继续处理,这样一个线程可以同时处理多个请求,而不需要浪费时间阻塞在IO上。它的性能和可伸缩性比同步模型要好很多,但带来的缺点就是请求处理流程被切割了,如发起请求跟处理请求需要分割成两个不同的过程,这样编写起来比较复杂,也容易出错。

我们的逻辑服务器用的就是单线程异步的框架,由于每个请求处理都需要访问后端好几个服务器,这样一来每次流程都不得不实现一个很复杂的状态机,写起程序来灰常蛋疼,后来如果有人接手的话阅读起来也是痛苦。

因此趁着这些天比较闲,就思索着研究下能否利用基于协程的一些框架来简化逻辑服务器的开发过程。其实找到两个不错的库,一个是orchid,另外一个是libtask,两个都可以当成框架来使用了,实现了基本的网络IO处理green化、调度器,以及协程间的通信。orchid基于boost的,这边没有使用boost的先例,可以直接先排除掉了。但是如果使用libtask的话,由于我们这边的框架封装是基于libevent的,libevent本身有自己的事件循环,libtask要求主线程需要阻塞在它的调度器循环当中,这样就需要将libevent的事件循环封装成一个协程,但是libevent的事件循环每次阻塞的时长是经过计算定时器最小值等来定的,这样一来可能会破坏libevent定时器的准确性,还有它的事件的即时分发。

最终还是决定根据现有框架来实现这个机制。但是如何去实现协程呢?看了一下boost那边的,为了跨平台,每个平台对应下都实现了一套,还是汇编的,这个看得我头晕,直接放弃。google了一下,有用setjmp和longjmp来实现的。最后找到这样一个东东–ucontext_t,这是linux下原生实现的,并提供了makecontext和swapcontext来修改上下文和切换上下文。

既然不想破坏libevent的事件循环,那当然不能自己实现一个单独的调度器了,看来只能利用libevent的事件分发了,让它来驱动事件的调度。

下面是一些主要的接口声明:

这里可以看到协程任务结构体中定义了pipe_fd这个数组,其实这里保存着一对匿名管道的读写描述符。一个协程任务的开始,将会由libevent进行驱动。当创建一个协程任务时,会先修改task_context的执行函数为一个默认的执行函数,这个执行函数会调用用户传进来的fn,参数为属于它的协程任务结构体和用户传进来的arg;然后新建一对匿名管道,将读描述符添加到libevent监听队列当中,回调为内部默认的回调函数,参数为协程任务结构体;最后往管道写描述符write一个字节的数据,创建过程完成。

当libevent触发创建任务时添加的回调函数时,回调函数会将上下文切换到协程结构体中task_context保存的上下文,这时候协程任务处理开始了。

这里提供的都比较底层,仅仅有任务的创建,挂起和恢复等。而且不能在协程任务当中再新建一个协程任务,以避免可能导致出错的可能性。

由于我们使用的框架并不会直接去读写套接描述符,因此这里自己仅仅将原来发送请求的异步接口进行了包装,实际上整个过程还是异步的,只是将发送请求的接口经过包装后让逻辑的编写可以直接在同一个函数里面顺序完成。也做了初步的性能测试,在原来框架的基础上,接收到请求后立即请求后端,等后端返回后立即响应给客户端,加了协程实现后并没有看出性能损耗。

这里就不放出完整的代码实现了,因为是依赖于自己内部的框架,放出来并没有多大的用处,有兴趣的可以搜索下libtask自行研究下。接下去可能会将libtask整合进原来的框架,这样一来就不用依赖于libevent的事件循环来驱动调度过程了,目前的方式等于每个请求都要开一对匿名管道,而且编写者需要小心溢溢,没法把每个协程任务都单独区分开来,且没有协程间通信的方式,还是很不完善的。

标签: , , , ,
文章分类 Programming, Unix/Linux

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*