libevent源代码分析之优先级

时间:2021-12-01 16:54:02
在学习基础API的时候,遇到过两个函数:
int event_base_priority_init(struct event_base *base, int n_priorities); 
以及
int event_priority_set(struct event *event, int priority);


阅读相关文档后得知, 第一个函数用于设置整个event_base的最大优先级数值

第二个函数则针对某个event设置其优先级.(优先级的值越大, 优先级越低; 值越小, 优先级越高)


没怎么接触过event_base, 那来看看event初始化中对优先级的默认处理方式:
在event_new ---> event_assign 中, 有这么一句话:
ev->ev_pri = base->nactivequeues / 2;
可见, 一般事件的初始化后优先级都是被设置为最大优先级数值的一半的


接下来,我们就来仔细分析这个优先级, 自然是先从event_base下手的:

struct event_base {
        ... ...

    /** Set if we should terminate the loop once we're done processing
     * events. */
        //设置了这个标志表示我们会中断当前的循环(但是会把当前正在执行的事件处理程序处理完)
    int event_gotterm;
    /** Set if we should terminate the loop immediately */
        //结束当前的循环
    int event_break;
    /** Set if we should start a new instance of the loop immediately. */
        //终止当前的循环,重新从头开始这个循环 (就跟continue含义类似)
    int event_continue;

    /** The currently running priority of events */
        //当前正在执行的事件的优先级
    int event_running_priority;

    /* Active event management. */
    /** An array of nactivequeues queues for active events (ones that
     * have triggered, and whose callbacks need to be called).  Low
     * priority numbers are more important, and stall higher ones.
     */
        //可以看出, 这里激活队列并不是只有一个,而是存在了一个激活队列的数组
        //根据优先级的不同划分,优先级多大, 数组就多大
    struct event_list *activequeues;
    /** The length of the activequeues array */
        //这个其实就等于最大优先级数值
    int nactivequeues;

        ... ...

    /** List of defered_cb that are active.  We run these after the active
     * events. */
        //被延迟的已激活的回调函数
    struct deferred_cb_queue defer_queue;
   
        ... ...
};


单纯的看这些结构体中的成员并没有什么太大的意义, 我们就地分析event_base_priority_init的代码:
int
event_base_priority_init(struct event_base *base, int npriorities)
{
    int i;
/*
#define N_ACTIVE_CALLBACKS(base)                                        \
         ((base)->event_count_active + (base)->defer_queue.active_count)
可以看出,这个宏是用于计算当前base已激活的event的总数加上被延迟的已激活的事件的总数的总合
也就是说,只要有事件已经被激活了或者可以假设说base已经开始运作了,再执行此函数是非法的!因此要求尽量在base开始之前就初始化这个最大优先级数值
*/
    if (N_ACTIVE_CALLBACKS(base) || npriorities < 1
        || npriorities >= EVENT_MAX_PRIORITIES)
        return (-1);

    if (npriorities == base->nactivequeues)
        return (0);

        //根据上面的判断可知, 如果此时没有激活事件优先级也是可以被重新设置的, 所以若之前已存在优先级队列,且此时没有已激活的事件存在在这些优先级丢了上, 那么可以释放掉这个数组
    if (base->nactivequeues) {
        mm_free(base->activequeues);
        base->nactivequeues = 0;
    }

        //以上为初始化, 以下为根据提供的npriorities作出处理
    /* Allocate our priority queues */
    base->activequeues = (struct event_list *)
      mm_calloc(npriorities, sizeof(struct event_list));
    if (base->activequeues == NULL) {
        event_warn("%s: calloc", __func__);
        return (-1);
    }
    base->nactivequeues = npriorities;

        //初始化每个队列
    for (i = 0; i < base->nactivequeues; ++i) {
        TAILQ_INIT(&base->activequeues[i]);
    }

    return (0);
}


以上了解了设置整个event_base的优先级, 现在看看设置每个事件的优先级
int
event_priority_set(struct event *ev, int pri)
{
    _event_debug_assert_is_setup(ev);

        //不能对已经被激活的事件设置优先级, 这也在情理之中. 这也要求我们在开启event_base循环之前如果需要就要事先设置事件优先级, 否则一旦开始,事件随时可能变为激活状态
    if (ev->ev_flags & EVLIST_ACTIVE)
        return (-1);
    if (pri < 0 || pri >= ev->ev_base->nactivequeues)
        return (-1);

    ev->ev_pri = pri;

    return (0);
}



最后, 我们也知道了, 每个事件都有其优先级存在, 在事件被激活后也会根据其优先级插入到优先级队列中去
既然在源代码中我们看到每个优先级队列是由TAILQ实现的, 那么其插入方式也就很简单的使用对应的插入方式了