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

本文详细介绍了如何将定时事件集成到libevent的主循环中,通过timeout_next和timeout_process两个关键函数,计算等待时间和处理超时事件。在主循环中,利用多路I/O机制如epoll的定时功能,集中处理定时事件,提高效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

在本小节中,我们将展开对定时事件的研究。首先还是和研究信号事件部分一样,先看看它是如何集成到多路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_nextdispatchtimeout_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);
}

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

  1. 取得最快超时的时间
  2. 将其与当前时间比较
  3. 如若小于等与当前时间,证明已经超时了,将tv置成0,返回之后,dispatch会很快将其激活(等待时间都赋成0了)
  4. 如若大于当前时间,证明还有段时间需要等待,调用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);
        }
}

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

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

小结

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值