前言
在本小節中,我們將展開對定時事件的研究。首先還是和研究信號事件部分一樣,先看看它是如何集成到多路I/O中的(或者說是如何與event_base聯繫起來的)。
如何將定時事件集成到主循環中
由於seletc、poll、epoll這類多路I/O機制支持定時,所以將定時事件集成到主循環中比起信號事件容易的多。我們只需要將定時事件註冊到小根堆上,然後根據堆頂(最短超時事件)來計算多路I/O機制需要等待的最大超時時間,這樣超時之後,就可以處理就緒的定時事件了。
在主循環中,有這樣一段代碼:
if (!base->event_count_active && !(flags & EVLOOP_NONBLOCK)) {
/* 沒有激活的事件並且是非阻塞時 */
timeout_next(base, &tv_p);
} else {
/*
* if we have active events, we just poll new events
* without waiting.
*/
evutil_timerclear(&tv);
}
...
res = evsel->dispatch(base, evbase, tv_p);
...
timeout_process(base);
timeout_next
用於計算主循環最大的等待時間,這裏將tv_p
賦值爲等待的時間。
然後evsel->dispatch
調用具體的多路I/O機制的等待函數,比如epoll的epoll_wait
,最後一個參數設置的定時時間就是timeout_next
計算得出的時間。這樣的話,就不用來一個定時事件就調用epoll_wait
等待,而是將定時事件都集中在一起處理。
在這段代碼中,有三個核心函數timeout_next
、dispatch
、timeout_process
。接下來我們會介紹第一個和最後一個,dispatch
放在關於多路I/O機制集成部分的小節講。
timeout_next
static int
timeout_next(struct event_base *base, struct timeval **tv_p)
{
struct timeval now;
struct event *ev;
struct timeval *tv = *tv_p;
/* min_heap_top返回處於小根堆的堆頂的定時事件 */
if ((ev = min_heap_top(&base->timeheap)) == NULL) {
/* if no time-based events are active wait for I/O */
//沒有定時時間,講等待時間賦爲null,返回
*tv_p = NULL;
return (0);
}
//獲取當前時間
if (gettime(base, &now) == -1)
return (-1);
/*
* 比較當前最快超時的時間與當前時間
* 小與等與:不用等待了,直接返回
*/
if (evutil_timercmp(&ev->ev_timeout, &now, <=)) {
evutil_timerclear(tv);
return (0);
}
//計算需要等待的時間,即超時時間-當前時間,並將結果賦給tv
evutil_timersub(&ev->ev_timeout, &now, tv);
assert(tv->tv_sec >= 0);
assert(tv->tv_usec >= 0);
event_debug(("timeout_next: in %ld seconds", tv->tv_sec));
return (0);
}
該函數的作用就是計算需要等待的時間,步驟如下:
- 取得最快超時的時間
- 將其與當前時間比較
- 如若小於等與當前時間,證明已經超時了,將
tv
置成0,返回之後,dispatch
會很快將其激活(等待時間都賦成0了) - 如若大於當前時間,證明還有段時間需要等待,調用
evutil_timersub
函數做個減法,將等待時間賦給tv
tv
指向tv_p
,而tv_p
用於給dispatch
指定超時時間。
timeout_process
void
timeout_process(struct event_base *base)
{
struct timeval now;
struct event *ev;
if (min_heap_empty(&base->timeheap))
return;
gettime(base, &now);
//取得小根堆頂的事件,即最有可能已經超時的事件。
while ((ev = min_heap_top(&base->timeheap))) {
//如果當前定時事件的時間還沒達到,直接退出循環
if (evutil_timercmp(&ev->ev_timeout, &now, >))
break;
/* delete this event from the I/O queues */
event_del(ev);
event_debug(("timeout_process: call %p",
ev->ev_callback));
//激活
event_active(ev, EV_TIMEOUT, 1);
}
}
這個函數的邏輯也很簡單:
- 把小根堆頂的事件的超時時間拿來和當前時間比較
- 如果大於,則證明時間還沒到,那麼小根堆其餘的事件也不可能超時了
- 如果小於,證明該事件已經滿足激活條件了(超時),將其激活
- 重複查看新的小根堆頂是否滿足條件,直至不滿足條件或者堆中已經沒有事件了。
小結
在本小節中,我們知道了定時事件是如何集成到一起讓主循環去處理的,並且仔細看了timeout_next
以及timeout_process
這兩個重要的函數,在下一小節中,我們將看到關於時間管理部分的函數,即timeout_correct
還有時間的加減、獲取等。