libev源码解析——调度策略

时间:2022-06-17
本文章向大家介绍libev源码解析——调度策略,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

        在《libev源码解析——监视器(watcher)结构和组织形式》中介绍过,监视器分为[2,-2]区间5个等级的优先级。等级为2的监视器最高优,然后依次递减。不区分监视器类型和关联的文件描述符的值,权限高的要优先于权限低的执行。但是ANFD结构中的监视器链表无法满足高等级优先执行的特性。那么libev是如何解决这个问题的呢?(转载请指明出于breaksoftware的csdn博客)

        anfds结构是以文件描述符作为索引的,其关心的是该描述符对应的事件是否发生。那我们关心不同等级执行顺序时,要以什么作为索引呢?那当然是等级值。libev也的确是这么做的

VAR (pendings, ANPENDING *pendings [NUMPRI])
VAR (pendingmax, int pendingmax [NUMPRI])
VAR (pendingcnt, int pendingcnt [NUMPRI])

        NUMPRI是等级的个数,其定义是

#define NUMPRI (EV_MAXPRI - EV_MINPRI + 1)

        pendings是一个具有5(2-(-2)+1))个元素的数组,不同等级和数组下标的对应关系通过下面这个宏来换算

# define ABSPRI(w) (((W)w)->priority - EV_MINPRI)

        可见高等级的位于数组末尾,低等级的位于数组头部。即等级为2的pendings数组下标是4,而等级为-2的下标是0。

        pendingmax记录的是每个等级已经记录的监视器个数。

        pendingcnt记录的是每个等级中当前有效的监视器个数。这个值和ev_watcher中pending值有很大的相关性,之后我们会去将讨论。

        pendings的每个元素是一个ANPENDING指针,其定义如下

typedef ev_watcher *W;

/* stores the pending event set for a given watcher */
typedef struct
{
  W w;
  int events; /* the pending event set for the given watcher */
} ANPENDING;

        成员变量w是一个ev_watcher指针,它指向anfds中一个监视器。我们看到这个结构中没有指向自身的指针,如next、pre之类,那就说明ANPENDING是用数组结构保存的,而非动态链表。

        那么anfds中的数据是如何转移到pendings上的呢?这个工作是由ev_feed_event函数完成

void noinline
ev_feed_event (EV_P_ void *w, int revents) EV_THROW
{
  W w_ = (W)w;
  int pri = ABSPRI (w_);

        w是监视器变量指针,revents是发生了的事件。ABSPRI宏将监视器中的等级转换成pendings数组下标,从而确定该监视器属于哪个数组。

        在一次循环前,每个监视器的pending位都将是0。因为对于没有触发的事件,其默认是0;而对于本次触发的事件,则在事件对应的回调函数被执行前,pending值被设置为0。该pending位的作用是用于记录该监视器信息在相应等级pendings数组的子数组中的位置。

        假如这个事件在一次循环中被触发两次。则第一次它会走入else的逻辑,根据pendingcnt中相应等级找到其应该属于的pending位数。如果此时pandings空间不足,则需要使用array_needsize重新分配并填充该空间;第二次时,pending位已经确定,此时只要更新events字段即可。

  if (expect_false (w_->pending))
    pendings [pri][w_->pending - 1].events |= revents;
  else
    {
      w_->pending = ++pendingcnt [pri];
      array_needsize (ANPENDING, pendings [pri], pendingmax [pri], w_->pending, EMPTY2);
      pendings [pri][w_->pending - 1].w      = w_;
      pendings [pri][w_->pending - 1].events = revents;
    }

  pendingpri = NUMPRI - 1;
}

        pendings里保存的是事件已经被触发的监视器信息,这就包括回调已经被调用的和即将被调用的。对于回调已经被调用过的监视器,libev不会将其从数组中去掉,而只是简单的将其pending值设置为0。那么本次循环要遍历的ANPENDING元素个数可能比数组个数要少,其个数是pendingcnt数组中相应等级作为下标对应的值。

        pendings中将数据准备好后,libev使用EV_INVOKE_PENDING宏遍历本次循环中更新的ANPENDING对象,调用其回调函数。

# define EV_INVOKE_PENDING ev_invoke_pending (EV_A)

void noinline
ev_invoke_pending (EV_P)
{
  pendingpri = NUMPRI;

  while (pendingpri) /* pendingpri possibly gets modified in the inner loop */
    {
      --pendingpri;

      while (pendingcnt [pendingpri])
        {
          ANPENDING *p = pendings [pendingpri] + --pendingcnt [pendingpri];

          p->w->pending = 0;
          EV_CB_INVOKE (p->w, p->events);
          EV_FREQUENT_CHECK;
        }
    }
}

#ifndef EV_CB_INVOKE
# define EV_CB_INVOKE(watcher,revents) (watcher)->cb (EV_A_ (watcher), (revents))
#endif

        最后我们看下包括函数调用的结构图