libco 是腾讯开源的一个协程库,主要应用于微信后台RPC框架,下面咱们从为何使用协程、如何实现协程、libco使用等方面了解协程和libco。程序员
why协程后端
为何使用协程,咱们先从server框架的实现提及,对于client-server的架构,server最简单的实现:微信
while(1) {accept();recv();do();send();}多线程
串行地接收链接、读取请求、处理、应答,该实现弊端显而易见,server同一时间只能为一个客户端服务。架构
为充分利用好多核cpu进行任务处理,咱们有了多进程/多线程的server框架,这也是server最经常使用的实现方式:框架
accept进程 - n个epoll进程 - n个worker进程异步
以上框架以事件监听、进程池的方式,解决了多任务处理问题,但咱们还能够对其做进一步的优化。socket
进程/线程是Linux内核最小的调度单位,一个进程在进行io操做时 (常见于分布式系统中RPC远程调用),其所在的cpu也处于iowait状态。直到后端svr返回,或者该进程的时间片用完、进程被切换到就绪态。是否能够把本来用于iowait的cpu时间片利用起来,发生io操做时让cpu处理新的请求,以提升单核cpu的使用率?分布式
协程在用户态下完成切换,由程序员完成调度,结合对socket类/io操做类函数挂钩子、添加事件监听,为以上问题提供了解决方法。函数
用户态下上下文切换
Linux提供了接口用于用户态下保存进程上下文信息,这也是实现协程的基础:
以上函数与保存上下文的 ucontext_t 结构都在 ucontext.h 中定义,ucontext_t 结构中,咱们主要关心两个字段:
stack_t 结构用于保存协程数据,该空间须要事先分配,咱们主要关注该结构中的如下两个字段:
获取进程上下文并切换的方法,总结有如下几步:
Socket族函数/io异步处理
当进程使用socket族函数 (connect/send/recv等)、io函数 (read/write等),咱们使用协程切换任务前,需对相应的fd设置监听事件,以便io完成后原有逻辑继续执行。
对io函数,咱们能够事先设置钩子,在真正调用接口前,对相应fd设置事件监听。一样,Linux为咱们设置钩子提供了接口,以read()函数为例:
当在prog程序中调用 read() 时,使用的就是咱们实现的 read() 函数。
对于glibc函数设置钩子的方法,可参考:Let's Hook a Librarg Function
libco
有了以上准备工做,咱们能够构建这样的server框架:
accept进程 - epoll进程(n个epoll协程) - n个worker进程(每一个worker进程n个worker协程)
该框架下,接收请求、业务逻辑处理、应答均可以看作单独的任务,相应的epoll、worker协程事先分配,服务流程以下:
libco 提供了如下接口:
socket族函数(socket/connect/sendto/recv/recvfrom等)、io函数(read/write) 在libco的co_hook_sys_call.cpp中已经重写,以read为例:
ssize_t read( int fd, void *buf, size_t nbyte ) { struct pollfd pf = { 0 }; pf.fd = fd; pf.events = ( POLLIN | POLLERR | POLLHUP ); int pollret = poll( &pf,1,timeout ); /*对相应fd设置监听事件*/ ssize_t readret = g_sys_read_func( fd,(char*)buf ,nbyte ); /*真正调用read()*/ return readret; }
小结
由最简单的单任务处理,到多进程/多线程(并行),再到协程(异步),server在不断地往极致方向优化,以更好地利用硬件性能的提高(多核cpu的出现、单核cpu性能不断提高)。
对程序员而言,可时常检视本身的程序,是否作好并行与异步,在硬件性能提高时,程序服务能力可不能够有相应比例的提高。