libevent事件循环的流程
libevent将IO事件、信号事件和定时器事件很好的结合在一起,采用了统一的事件源方式,即把信号事件也转换成IO事件,然后采用同一套IO复用机制去监听。
libevent的事件循环通过event_base_loop完成,另外一个事件循环函数是event_base_dispatch,其功能上即为没有设置标志的 event_base_loop(base, 0)。即event_base_dispatch()将一直运行,直到没有已经注册的事件了,或者调用了event_base_loopbreak()或者event_base_loopexit()为止。整个事件的循环流程如下图所示:
说明:
1 . 时间校正
- libevent中有个参数use_monotonic,该参数表示时间是否是单调增长的,该值为0则表示时间可能会往后调整,即往更早的时间去调。每一次循环时,需要判断时间是否需要调整,如果时间被往后调整至t1时刻,那么event_base中的所有事件都需要进行校正至t1时刻。
- libevent维护了两套方案管理时间基于时间的小顶堆min-heap,还有基于通用超时构成的链表队列。 两种方式在不同的场景下时间复杂度各有差异。基于min-heap的获取最小时间的事件复杂度为O(1)。
2 . 事件等待
在循环前,libevent会检查signal标志位是否被设置。若设置要监听SIGINT这个信号,那么在实现的内部就对SIGINT这个信号设置捕抓函数。此外,在实现的内部还要建立一条管道(pipe,写管道ev_signal_pair[0]),并把这个管道加入到多路IO复用函数中。当SIGINT这个信号发生后,捕抓函数将会被调用。而这个捕抓函数的工作就是往管道写入一个字符(这个字符往往等于所捕抓到信号的信号值)。此时,这个管道ev_signal_pair[1]就变成是可读的了,多路IO复用函数能检测到这个管道变成可读的了。换句话说就是多路IO复用函数检测到这个SIGINT信号发生了,这也就完成了对信号的监听工作。
3 . 事件执行
对于激活的事件执行方式:按优先级从高到低。因而,低优先级的事件可能不能立即得到执行。
事件循环源码分析
int
event_base_loop(struct event_base *base, int flags)
{
....
/* Grab the lock. We will release it inside evsel.dispatch, and again as we invoke user callbacks. */
EVBASE_ACQUIRE_LOCK(base, th_base_lock);
if (base->running_loop) {
event_warnx(&