libev源码解析——定时器原理

来源:互联网 发布:手机淘宝2015旧版本5.5 编辑:程序博客网 时间:2024/06/05 11:53

        本文将回答《libev源码解析——I/O模型》中抛出的两个问题。(转载请指明出于breaksoftware的csdn博客)

        对于问题1:为什么backend_poll函数需要指定超时?我们让其一直等待到有事件发生不是更好么?

        答案是“必须要指定超时”。为什么呢?在《libev源码解析——总览》中,我们抛出过一个问题:定时器和事件是如何关联的?因为libev是一个事件库,所以我们需要将定时器的逻辑也转换成事件相关操作。

        我们看下其实现原理。libev在初始化默认循环时调用了ev_default_loop方法,其会在底层调用evpipe_init方法。它会通过eventfd创建一个永远等不到的事件。这样我们就可以调整等待该事件的超时时间来达到定时执行的目的。

static void noinline ecb_coldevpipe_init (EV_P){  if (!ev_is_active (&pipe_w))    {      int fds [2];# if EV_USE_EVENTFD      fds [0] = -1;      fds [1] = eventfd (0, EFD_NONBLOCK | EFD_CLOEXEC);      if (fds [1] < 0 && errno == EINVAL)        fds [1] = eventfd (0, 0);      if (fds [1] < 0)# endif        {          while (pipe (fds))            ev_syserr ("(libev) error creating signal/async pipe");          fd_intern (fds [0]);        }      evpipe [0] = fds [0];      if (evpipe [1] < 0)        evpipe [1] = fds [1]; /* first call, set write fd */      else        {          /* on subsequent calls, do not change evpipe [1] */          /* so that evpipe_write can always rely on its value. */          /* this branch does not do anything sensible on windows, */          /* so must not be executed on windows */          dup2 (fds [1], evpipe [1]);          close (fds [1]);        }      fd_intern (evpipe [1]);      ev_io_set (&pipe_w, evpipe [0] < 0 ? evpipe [1] : evpipe [0], EV_READ);      ev_io_start (EV_A_ &pipe_w);      ev_unref (EV_A); /* watcher should not keep loop alive */    }}
        因为定时器并非是由事件触发而执行,而是由于事件没有触发导致等待超时而执行。所以backend_poll函数指针调用时,不可以一直等待下去,而要传递超时时间。从而让libev中利用“永远等不到的事件”相关的监视器有机会执行。
        利用等待超时这个思路非常有意思。但是又面临另一个问题,超时时间的选择?比如我们现在有两个定时器:2秒一次和3秒一次,那么超时时间该设置成多少呢?如果设置成2秒超时,那么3秒一次的定时器将被延期1秒执行(需要等待到第二个周期)。如果设置为3秒超时,2秒一次的定时器也将被延期1秒执行。如果设置成1秒超时,则超时导致循环的次数增多……这种固定超时的方案怎么都不太好。那么libev是如何解决这个问题的呢?
        libev在实现的内部不会有“定时”这样的概念,也就是说每次事件等待的时长是不确定的。这也是为什么各个IO模型需要暴露backend_poll方法的原因——需要每次指定超时的时间。
        那这个超时时间怎么计算?每个需要使用等待超时功能触发的监视器,都会在一个结构中保存下次触发的时间。以上面例子为例,并且假设没有其他事件的干扰,假如现在时间是12:00:00,则2秒一次定时器监视器(后称2秒监视器)的“下次执行时间”为12:00:02;3秒一次的定时器监视器(后称3秒监视器)的“下次执行时间”为12:00:03。那么本次等待的时间是离当前时间最近的2秒监视器“下次执行时间”减去当前时间,即12:00:02-12:00:00=2秒。等到时间为12:00:02时,2秒定时器会被执行,并且其“下次执行时间”修改成12:00:04。假设2秒定时器和本次循环中逻辑的执行时间消耗了0.5秒,此时时钟已经走到12:00:02.5。此时离现在最近的“下次执行时间”是3秒监视器,则下次循环的等待时间是12:00:03-12:00:02.5=0.5秒。于是12:00:03时,3秒监视器会被执行。

        上面例子解释了libev超时时间选择的基本原理。当然实际实现比这个稍微复杂一点,因为它要考虑相对时间定时器、绝对时间定时器、其他一些用户设置的事件以及各种IO模型的默认等待时间。
/* calculate blocking time */      {        ev_tstamp waittime  = 0.;        ev_tstamp sleeptime = 0.;        /* remember old timestamp for io_blocktime calculation */        ev_tstamp prev_mn_now = mn_now;        /* update time to cancel out callback processing overhead */        time_update (EV_A_ 1e100);        /* from now on, we want a pipe-wake-up */        pipe_write_wanted = 1;        ECB_MEMORY_FENCE; /* make sure pipe_write_wanted is visible before we check for potential skips */        if (expect_true (!(flags & EVRUN_NOWAIT || idleall || !activecnt || pipe_write_skipped)))          {            waittime = MAX_BLOCKTIME;            if (timercnt)              {                ev_tstamp to = ANHE_at (timers [HEAP0]) - mn_now;                if (waittime > to) waittime = to;              }#if EV_PERIODIC_ENABLE            if (periodiccnt)              {                ev_tstamp to = ANHE_at (periodics [HEAP0]) - ev_rt_now;                if (waittime > to) waittime = to;              }#endif            /* don't let timeouts decrease the waittime below timeout_blocktime */            if (expect_false (waittime < timeout_blocktime))              waittime = timeout_blocktime;            /* at this point, we NEED to wait, so we have to ensure */            /* to pass a minimum nonzero value to the backend */            if (expect_false (waittime < backend_mintime))              waittime = backend_mintime;            /* extra check because io_blocktime is commonly 0 */            if (expect_false (io_blocktime))              {                sleeptime = io_blocktime - (mn_now - prev_mn_now);                if (sleeptime > waittime - backend_mintime)                  sleeptime = waittime - backend_mintime;                if (expect_true (sleeptime > 0.))                  {                    ev_sleep (sleeptime);                    waittime -= sleeptime;                  }              }          }#if EV_FEATURE_API        ++loop_count;#endif        assert ((loop_done = EVBREAK_RECURSE, 1)); /* assert for side effect */        backend_poll (EV_A_ waittime);
        基于上面的分析,对于问题2:“符合条件的监视器”是否可以表述为“本次触发事件对应的监视器”?答案依然是否定的。因为定时器监视器对应的事件永远也不会被等待到,而它被执行只是因为时间到了。