(十二)定時事件集成到多路IO機制

前言

在本小節中,我們將展開對定時事件的研究。首先還是和研究信號事件部分一樣,先看看它是如何集成到多路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_nextdispatchtimeout_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);
}

該函數的作用就是計算需要等待的時間,步驟如下:

  1. 取得最快超時的時間
  2. 將其與當前時間比較
  3. 如若小於等與當前時間,證明已經超時了,將tv置成0,返回之後,dispatch會很快將其激活(等待時間都賦成0了)
  4. 如若大於當前時間,證明還有段時間需要等待,調用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);
        }
}

這個函數的邏輯也很簡單:

  1. 把小根堆頂的事件的超時時間拿來和當前時間比較
  2. 如果大於,則證明時間還沒到,那麼小根堆其餘的事件也不可能超時了
  3. 如果小於,證明該事件已經滿足激活條件了(超時),將其激活
  4. 重複查看新的小根堆頂是否滿足條件,直至不滿足條件或者堆中已經沒有事件了。

小結

在本小節中,我們知道了定時事件是如何集成到一起讓主循環去處理的,並且仔細看了timeout_next以及timeout_process這兩個重要的函數,在下一小節中,我們將看到關於時間管理部分的函數,即timeout_correct還有時間的加減、獲取等。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章