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:“符合条件的监视器”是否可以表述为“本次触发事件对应的监视器”?答案依然是否定的。因为定时器监视器对应的事件永远也不会被等待到,而它被执行只是因为时间到了。
阅读全文
0 0
- libev源码解析——定时器原理
- libev源码解析——定时器监视器和组织形式
- libev源码解析——总览
- libev源码解析——调度策略
- libev源码解析——I/O模型
- Libev源码解析
- Libev源码分析03:Libev使用堆管理定时器
- Libev源码分析04:Libev中的相对时间定时器
- Libev源码分析05:Libev中的绝对时间定时器
- Libev源码分析—常用watcher
- libev源码解析——监视器(watcher)结构和组织形式
- ss-libev 源码解析udp篇 (1)
- ss-libev 源码解析udp篇 (2)
- ss-libev 源码解析udp篇 (3)
- ss-libev 源码解析udp篇 (4)
- ss-libev 源码解析local篇(4): server_recv_cb之STAGE_STREAM
- thttpd源码解析 定时器模块
- thttpd源码解析 定时器模块
- 剑指offer:包含min函数的栈
- hdu4548 美素数(C语言)
- 架构师入门笔记三 初识Queue队列
- 最短路径—Dijkstra算法和Floyd算法(理解)
- <a>标签的href和onclick属性
- libev源码解析——定时器原理
- 2017.08.04爱可生电话面试笔记
- tensorflow 实现wgan-gp mnist图片生成
- Windows下使用Redis(一)安装使用
- hdu 3486 Interviewe (二分+RMQ)
- 前端面试经典题目
- Miller_Rabin 算法进行素数测试 pollard_rho 算法进行质因数分解 poj1811Prim Test
- 根文件的制作与挂接
- AWS自动化合规slide