1 解决问题
目前主流的协程库libgo(golang提供);libco(腾讯开源),libgo使用起来方便,自己的工程也是采用libgo实现的全球同服的服务器框架。解决了采用同步的方式实现异步的性能。
2 同步/异步/协程关系
1 同步与异步区别
对于同步来说是,业务服务器发送一个请求后,等待第三方服务器返回后继续执行其它操作;异步是不需要等待返回,直接执行其它操作,当第三方服务器返回时候,在通过回调方式来通知业务服务器对应响应的处理。
异步的实现方式可以看前面的博客(异步请求池实现)四元组.
2 具体编程实现
不采用线程池和采用线程池的对比
//不采用线程池的大体逻辑 while(1){ int nready = epoll_wait(); // 检测io for(i = 0; i < nready; i++){ recv(); // 对io操作 parese(); // 解析buffer send(); } }
// 加入线程池大体逻辑 void mainloop(){ // 主线程 while(1){ int nready = epoll_wait(); for(i = 0; i < nready; i++){ /*recv(buffer); parese(); send(); */ push_to_workqueue(work_cb); // 抛入线程池 } } } // 另外一个子线程 void *work_cb(void *arg){ recv(buffer); parese(); send(buff); }
注意加线程池的性能大概提升不加线程池的3倍左右。
对于服务器来说,当检查io和读写io是在同一个流程里就叫同步;而不在同一个流程(例如添加线程池后)就叫异步
区别:
同步实现简单,流程清晰符合人的思维方式,效率相对来说不高;
异步实现复杂,效率相对来说较高。
为了中和同步和异步,需要采用同步的方式实现异步的性能,两者优点都想要所以产生了协程.
3 异步和协程的关系
异步实现主要是在commit和callback两个部分。具体流程如下
对于协程来讲,在fd添加到epoll管理后,引入一个yield()原语操作让出操作,去执行其它的就绪的,在resume()恢复到现场,继续往下执行。
3 yield()和resume()原语实现方案
常见的实现方案:
a) setjmp/longjmp
b) ucontext(linux底层提供的)
c) 汇编代码实现跳转
对于第一种setjmp/longjmp实现起来比较简单在对应的位置调用api即可。
而汇编代码实现可读行更强,推荐采用第三种方式汇编实现(大部分情况下是直接用开源组件来调用即可)
两者底层都调用switch接口。
4) 协程切换
对于原语yield和resume操作底层都是通过switch(A,B)实现。以单cpu为例:来看协程切换流程:
协程A让出给B,将cpu寄存器值保存到协程A中(定义一组结构体struct context);
当切换的时候,加载协程B的数据到cpu寄存器中。)
5) 协程创建
协程框架会提供一个接口用于创建协程,创建完成的协程添加到就绪队列,协程的入口函数通过寄存器eip将协程函数保存起来。
6) 协程的定义
协程包括:就是要封装一个协程的结构体。
context上下文,stack栈空间,协程栈大小,协程入口函数,入口参数,协程的状态集合(wait,sleep,ready),exit,status状态
状态集合:wait状态采用红黑树或者队列实现,sleep休眠可采用红黑树和最小堆实现,ready状态可采用队列实现