libev源码解析——定时器原理
? ? ? ? 本文將回答《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_EVENTFDfds [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_ENABLEif (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;
#endifassert ((loop_done = EVBREAK_RECURSE, 1)); /* assert for side effect */backend_poll (EV_A_ waittime);
? ? ? ? 基于上面的分析,對于問題2:“符合條件的監視器”是否可以表述為“本次觸發事件對應的監視器”?答案依然是否定的。因為定時器監視器對應的事件永遠也不會被等待到,而它被執行只是因為時間到了。 ? ? ?
總結
以上是生活随笔為你收集整理的libev源码解析——定时器原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: libev源码解析——I/O模型
- 下一篇: libev源码解析——定时器监视器和组织