前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >libev源码解析——定时器原理

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

作者头像
方亮
发布2019-01-16 16:03:41
1.3K0
发布2019-01-16 16:03:41
举报
文章被收录于专栏:方亮方亮

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

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

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

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

static void noinline ecb_cold
evpipe_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:“符合条件的监视器”是否可以表述为“本次触发事件对应的监视器”?答案依然是否定的。因为定时器监视器对应的事件永远也不会被等待到,而它被执行只是因为时间到了。      

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017年08月07日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档