高性能网络库分析1——libevent
libevent事件循环分析
libevent库事件循环封装在event_base_loop()函数中,函数的主循环流程:
while(1){ if(base中有事件或loop需要阻塞){ timeout_next(base, &tv_p); // 从timer heap中取根节点的超时时间,作为IO复用的阻塞等待时间tv_p(tv_p指向tv) } else // 无需阻塞IO复用等待 evutil_timerclear(&tv) //IO复用阻塞等待时长清0- … clear_time_cache(base); // 清除base对象中系统时间缓存tv_catch 1) evsel->dispatch(base, tv_p); // 调用select(), poll(), epoll()等IO复用系统调用, IO及signal**事件检测 update_time_catch(base); // 跟新base对象的系统时间缓存 2) timeout_process(base); //定时器**事件检测 … 3) event_process_active(base); // 处理**的事件 } |
看起来过程很复杂,实际上归纳一下,一轮循环也就分为两个阶段:
1)**事件检测阶段
其中,由于libevent时间管理的策略原因,**事件检测阶段又分为两个子阶段:
1.1)定时器**事件检测,evsel->dispatch(base, tv_p) ,参数二为io复用阻塞超时值;
1.2)IO事件及signal事件检测;timeout_process(base)。
2)**事件处理阶段
遍历各级**事件优先级队列,逐队列逐事件调用事件回调函数,处理事件。
首先,谈下libevent库中时间管理的策略
libevent采用monotonic时间,作为定时器是否**的依据。(CLOCK_MONOTONIC:以绝对时间为准,获取的时间为系统重启到现在的时间,更改系统时间对它没有影响。)
用户调用,
evtimer_set(&ev,timeer_cb, NULL)
构造一个timer事件,然后调用,
event_add(&ev,&tv);
为定时器事件设置超时值,在event_add()函数内部会调用event_add_nolock_()函数判断插入事件的类型,然后将事件插入对因事件列表或者堆中,其中在处理定时器事件时,操作如下
int event_add_nolock_(struct event *ev, const struct timeval *tv, int tv_is_absolute){ … if (res != -1 && tv != NULL){ // 表明事件类型为定时器 … // 将当前MONOTONIC时间+用户设定的定时器超时值作为定时器超时**的绝对时间 evutil_timeradd(&now, tv, &ev->ev_timeout); … // 将定时器事件插入到小根堆中 event_queue_insert_timeout(base, ev); } … } |
可以看到,实际上事件真正的超时值是一个绝对时间。所以,若将libevent事件循环看做一个生命周期,定时器事件则是生命周期中的固定点(同步点),而IO&signal事件由于是异步发生的,所以是生命周期中的异步点,如下图:
libevent定时器与muduo定时器的区别:
libevent库中定时器机采用一种弱定时器机制,利用select()等IO复用系统调用的阻塞时长来实现定时器功能,因此使定时器具备平台无关性,但缺点使不太精确,且要将定时器事件的处理与其他事件分开处理。
而muduo库针对linux平台设计,定时器采用timerfd定时器文件描述符,虽然在精准度上也没比libevent提高多少,但由于使用文件描述符,使得定时器事件的处理与IO,文件等事件的处理统一,利于开发。
回到事件主循环,简单分析下2个阶段核心函数的操作
1)evsel->dispatch()
这个部分,实际上就是封装了IO复用那一套东西,根据平台对IO复用的支持,libevent可以使用select(),poll(),epoll(),kqueue()。网上有大量相关资料,不再赘述。
2)timerout_process(base)
首先,这个函数的命名方式非常容易引起歧义,一开始我以为这个函数是为了处理因超时被**的定时器事件,结果阅读源码后发现这个原来是检测timer heap中哪些定时器事件超时并**他们的函数。和evsel->dispatch()同属**事件检测阶段…,下面是关键代码:
timeout_process(base){ gettime(base, &now); // 获取当前系统绝对时间 while(不断从timer heap根取定时器事件用于检测){ if(evutil_timercmp(ev->ev_timeout, now, >)) { // 这是一个宏,实际就是比对一个定时器事件的超时时间与当前时间,若堆顶定时器超时时间晚于当前时间,说明堆中无能**事件,退出循环。 break; } // else // 存在能**的定时器事件,处理描述如下 { /*首先,将事件从timer heap中取下, 然后,检测事件是否已在**队列,若在就从**队列中移除(重置事件)*/ event_del_nolock_(ev, EVENT_DEL_NOBLOCK); /*最后,将定时器事件插入到**队列中等待处理*/ event_active_nolock_(ev,EV_TIMEOUT,1); } } } |
3)event_process_active(base)
event_process_active()函数处理经过evsel->dispatch()函数检测过得的**事件。libevent库将**事件按优先级组织为多个优先级链表。event_process_active函数的职责就是按优先级从高到低处理**事件(调用其事件句柄)。
event_process_active(){ for(inti=0;i<base→nativequeues;i++){ activeq= &base->activequeues[i]; //参数二为回调函数为某一优先级梯队的**事件回调函数链表,参数三为数组最大回调函数个数,函数处理一个优先级梯队下所有**事件 event_process_active_single_queue(base,activeq,maxcb,…) } } |
其中event_process_active_single_queue处理一个优先级梯度下所有**事件,过程如下:
structevent_callback *evcb; for(/*遍历回调函数列表-> evcb*/){ /*先从**队列中删除事件*/ event_queue_remove_active(base,evcb); /*然后根据evcb对象的类型调用事件句柄*/ switch(evcb→evcb_closure){ //此处用到closure一词,可能作者思路是事件关闭前调用的意思 caseEV_CLOSURE_EVENT_SIGNAL: event_signal_closure(base,ev); //处理signal break; caseEV_CLOSURE_EVENT_PERSIST: event_persist_closure(base,ev); //处理周期性定时器事件 break; caseEV_CLOSURE_EVENT: evcb_callback(ev->ev_fd,res, ev→ev_arg); // IO事件,timer事件 break; ... } } |