libevent源码分析之带有定时器的事件

关于超时event, 一开始接触libevent时遇到的example就是它, 小巧而简单易懂, 但是其内部却因为种种因素而十分庞杂.
一个单纯的定时器event如下定义:
#define evtimer_new(b, cb, arg)        event_new((b), -1, 0, (cb), (arg))
其实, 如果需要一个event拥有定时器的功能, 都只需要在event_add时将第二个参数不置为NULL就行


如何添加定时器

接下来, 我们就来看看event_add中如何添加定时器的:
在看如何添加定定时器之前, 要声明的是libevent在最初是只使用小顶堆对定时器进行管理的, 后来发现可能会在同一时间处理许多具有相同超时时间的事件, 这样一来, 使用小顶堆就没有优势了, 于是有使用common timeout进行辅助处理,以提升性能(先不谈, 文章最后部分讨论)
//在了解event_add函数内部源码之前,我们先看看官方对于event_add的介绍
//if you call event_add on an event that is already pending, it will leave it pending, and reschedule it with the provided timeout
//if the event, is already pending ,and you re-add it with the timeout NULL, event_add will have no effect
static inline int
event_add_internal(struct event *ev, const struct timeval *tv,
    int tv_is_absolute)
{

        ... ...                        //一些准备

    /*
     * prepare for timeout insertion further below, if we get a
     * failure on any step, we should not change any state.
     */
    //接下来我们就要开始正式插入过程了
    //只是当我们调用此函数时,我们可能做几件事情:将事件注册到event_base中; 若事件有定时器则把事件放到时间堆中/链表中
    //这两件事要么一起做,要么都不做,需要有原子的性质,所以这里采取了行动
    //这里我们发现此事件拥有定时器(tv!=NULL), 且它尚未被添加到超时事件链表中去
    //于是我们调用min_heap_reserve函数,目的只是增加堆的大小,提前为我们插入定时器作准备
    //还要注意的是,我们只是做准备,没有真的插入,因为要满足原子性,此时插入定时器肯定是会成功的,但如果下面的事件插入失败了,那就打破原子性了
    //如果下面事件插入失败了,那也就退出了,也不会插入定时器,所以能形成原子性,只是会导致可能的堆容量增大,影响不大
    if (tv != NULL && !(ev->ev_flags & EVLIST_TIMEOUT)) {
        if (min_heap_reserve(&base->timeheap,
            1 + min_heap_size(&base->timeheap)) == -1)
            return (-1);  /* ENOMEM == errno */
    }


        ... ...                        //假设事件的插入成功了


    /*
     * we should change the timeout state only if the previous event
     * addition succeeded.
    */
    //现在,判断上面的事件插入操作是否成功,成功的话我们处理定时器了
    //再来回顾一下此函数的说明:
    //if you call event_add on an event that is already pending, it will leave it pending, and reschedule it with the provided timeout
    //if the event, is already pending ,and you re-add it with the timeout NULL, event_add will have no effect
    if (res != -1 && tv != NULL) {
        struct timeval now;
        int common_timeout;

        /*
         * for persistent timeout events, we remember the
         * timeout value and re-add the event.
         *
         * If tv_is_absolute, this was already set.
         */
        //对于永久(带EV_PERSIST标志)的事件当然带有永久的定时器,我们记住它的超时时间,每次超时结束后继续通过该时间设置定时器
        //如果是绝对时间的,似乎也没必要设置PERSIST标志了
        //所以现在我们通过ev_io_timeout记住这个超时时间供下次使用,这个ev_io_timeout写具体了就是:
        //    ev->_ev.ev_io.ev_timeout
        //如果events中包含EV_PERSIST属性,那么下面的closure标识是在调用event_assign时被设置的
        if (ev->ev_closure == EV_CLOSURE_PERSIST && !tv_is_absolute)
            ev->ev_io_timeout = *tv;

        /*
         * we already reserved memory above for the case where we
         * are not replacing an existing timeout.
         */    
        //如果这个事件之前就有定时器,现在又对它调用event_add,那么就要覆盖之前的定时器
        //之所以会出现这样的情况, 大致是因为事件在被触发时就被删除了, 但是触发该事件的不是超时原因, 这里再次添加该事件, 那么超时时间就被重置了
        //举个例子, 接收用户的数据, 保持长连接. 如果一段时间没有收到就断开连接. 但是在超时时间内收到了用户数据, 那么此时就要重置定时器
        if (ev->ev_flags & EVLIST_TIMEOUT) {
            /* XXX I believe this is needless. */
            //判断此事件是否为堆顶元素,如果是的话,就需要提醒主线程
            if (min_heap_elt_is_top(ev))
                notify = 1;

            //将此定时器从定时器队列中删除,因为新的超时时间会覆盖旧的超时
            //虽然一个event可以被多次evnet_add, 但是对于它的定时器来说却只能有一个
            event_queue_remove(base, ev, EVLIST_TIMEOUT);
        }

        /* Check if it is active due to a timeout.  Rescheduling
         * this timeout before the callback can be executed
         * removes it from the active list. */
        //如果此事件已经被激活了, 且激活的原因正是超时, 那么我们在执行其回调函数前将其移除激活队列
        if ((ev->ev_flags & EVLIST_ACTIVE) &&
            (ev->ev_res & EV_TIMEOUT)) {
            if (ev->ev_events & EV_SIGNAL) {
                /* See if we are just active executing
                 * this event in a loop
                 */
                if (ev->ev_ncalls && ev->ev_pncalls) {
                    /* Abort loop */
                    *ev->ev_pncalls = 0;
                }
            }

            event_queue_remove(base, ev, EVLIST_ACTIVE);
        }

        //得到当前时间
        gettime(base, &now);

        //暂且不谈common_timeout, 下面会详细分析. 这里一段代码目的是得到此事件的绝对超时时间
        common_timeout = is_common_timeout(tv, base);
        if (tv_is_absolute) {
            //用ev_timeout记录下此事件要在绝对时间tv发生
            ev->ev_timeout = *tv;
        } else if (common_timeout) {
            struct timeval tmp = *tv;
            tmp.tv_usec &= MICROSECONDS_MASK;
            evutil_timeradd(&now, &tmp, &ev->ev_timeout);
            ev->ev_timeout.tv_usec |=
                (tv->tv_usec & ~MICROSECONDS_MASK);
        } else {
            evutil_timeradd(&now, tv, &ev->ev_timeout);
        }

        //将拥有定时器的事件也放到超时队列中去, 这样flags中就有了EVLIST_TIMEOUT
        //与此同时, 将定时器插入到小顶堆或者common timeout中去
        event_queue_insert(base, ev, EVLIST_TIMEOUT);
        if (common_timeout) {
            struct common_timeout_list *ctl =
                get_common_timeout_list(base, &ev->ev_timeout);
            if (ev == TAILQ_FIRST(&ctl->events)) {
                common_timeout_schedule(ctl, &now, ev);
            }
        } else {
      
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值