(十二)定时事件集成到多路IO机制

xiaoxiao2021-02-28  117

前言

在本小节中,我们将展开对定时事件的研究。首先还是和研究信号事件部分一样,先看看它是如何集成到多路I/O中的(或者说是如何与event_base联系起来的)。

如何将定时事件集成到主循环中

由于seletc、poll、epoll这类多路I/O机制支持定时,所以将定时事件集成到主循环中比起信号事件容易的多。我们只需要将定时事件注册到小根堆上,然后根据堆顶(最短超时事件)来计算多路I/O机制需要等待的最大超时时间,这样超时之后,就可以处理就绪的定时事件了。

在主循环中,有这样一段代码:

if (!base->event_count_active && !(flags & EVLOOP_NONBLOCK)) { /* 没有激活的事件并且是非阻塞时 */ timeout_next(base, &tv_p); } else { /* * if we have active events, we just poll new events * without waiting. */ evutil_timerclear(&tv); } ... res = evsel->dispatch(base, evbase, tv_p); ... timeout_process(base);

timeout_next用于计算主循环最大的等待时间,这里将tv_p赋值为等待的时间。 然后evsel->dispatch调用具体的多路I/O机制的等待函数,比如epoll的epoll_wait,最后一个参数设置的定时时间就是timeout_next计算得出的时间。这样的话,就不用来一个定时事件就调用epoll_wait等待,而是将定时事件都集中在一起处理。

在这段代码中,有三个核心函数timeout_next、dispatch、timeout_process。接下来我们会介绍第一个和最后一个,dispatch放在关于多路I/O机制集成部分的小节讲。

timeout_next

static int timeout_next(struct event_base *base, struct timeval **tv_p) { struct timeval now; struct event *ev; struct timeval *tv = *tv_p; /* min_heap_top返回处于小根堆的堆顶的定时事件 */ if ((ev = min_heap_top(&base->timeheap)) == NULL) { /* if no time-based events are active wait for I/O */ //没有定时时间,讲等待时间赋为null,返回 *tv_p = NULL; return (0); } //获取当前时间 if (gettime(base, &now) == -1) return (-1); /* * 比较当前最快超时的时间与当前时间 * 小与等与:不用等待了,直接返回 */ if (evutil_timercmp(&ev->ev_timeout, &now, <=)) { evutil_timerclear(tv); return (0); } //计算需要等待的时间,即超时时间-当前时间,并将结果赋给tv evutil_timersub(&ev->ev_timeout, &now, tv); assert(tv->tv_sec >= 0); assert(tv->tv_usec >= 0); event_debug(("timeout_next: in %ld seconds", tv->tv_sec)); return (0); }

该函数的作用就是计算需要等待的时间,步骤如下:

取得最快超时的时间将其与当前时间比较如若小于等与当前时间,证明已经超时了,将tv置成0,返回之后,dispatch会很快将其激活(等待时间都赋成0了)如若大于当前时间,证明还有段时间需要等待,调用evutil_timersub函数做个减法,将等待时间赋给tv

tv指向tv_p,而tv_p用于给dispatch指定超时时间。

timeout_process

void timeout_process(struct event_base *base) { struct timeval now; struct event *ev; if (min_heap_empty(&base->timeheap)) return; gettime(base, &now); //取得小根堆顶的事件,即最有可能已经超时的事件。 while ((ev = min_heap_top(&base->timeheap))) { //如果当前定时事件的时间还没达到,直接退出循环 if (evutil_timercmp(&ev->ev_timeout, &now, >)) break; /* delete this event from the I/O queues */ event_del(ev); event_debug(("timeout_process: call %p", ev->ev_callback)); //激活 event_active(ev, EV_TIMEOUT, 1); } }

这个函数的逻辑也很简单:

把小根堆顶的事件的超时时间拿来和当前时间比较如果大于,则证明时间还没到,那么小根堆其余的事件也不可能超时了如果小于,证明该事件已经满足激活条件了(超时),将其激活重复查看新的小根堆顶是否满足条件,直至不满足条件或者堆中已经没有事件了。

小结

在本小节中,我们知道了定时事件是如何集成到一起让主循环去处理的,并且仔细看了timeout_next以及timeout_process这两个重要的函数,在下一小节中,我们将看到关于时间管理部分的函数,即timeout_correct还有时间的加减、获取等。

转载请注明原文地址: https://www.6miu.com/read-49813.html

最新回复(0)