1. 程式人生 > >Libevent學習-------定時器事件

Libevent學習-------定時器事件

定時器事件的建立

Libevent 一般呼叫evtimer_new來定義一個定時器事件

#define evtimer_new(b, cb, arg)        event_new((b), -1, 0, (cb), (arg))

從巨集定義來看,這個事件和io、signal事件的區別在於fd項為-1,表示並不關注, 並且events項為0, 並不是想象中的EV_TIMEOUT.
evtimer_new只是初始化了一個一般的超時事件,而真正將一個事件進行超時處理是在
event_add函式的第二個引數必須指定一個超時時間。
總結來說,不管event_new建立了一個什麼型別的event,如果在event_add的第二個引數添加了一個超時時間,則該事件就會進行超時處理了。

定時器事件例項

[email protected]:~/test/libevent/my_libevent_test>more libevent_test_timeout.c 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <event.h>
#include <time.h>

void do_timeout(evutil_socket_t fd, short event, void *arg)
{
    printf("do timeout (time: %ld)!\n"
, time(NULL)); } void create_timeout_event(struct event_base *base) { struct event *ev; struct timeval timeout; //ev = evtimer_new(base, do_timeout, NULL); ev = event_new(base, -1, EV_PERSIST, do_timeout, NULL); if (ev) { timeout.tv_sec = 5; timeout.tv_usec = 0; event_add(ev, &timeout); } } int
main(int argc, char *argv[]) { struct event_base *base; base = event_base_new(); if (!base) { printf("Error: Create an event base error!\n"); return -1; } create_timeout_event(base); event_base_dispatch(base); return 0; }

event_add 對定時器事件的處理

event_add ——>event_add_internal
通過event_add呼叫event_add_internal時,tv_is_absolute為0.
event結構中的events是event_new時候傳入的引數,如

/**
 * @name event flags
 *
 * Flags to pass to event_new(), event_assign(), event_pending(), and
 * anything else with an argument of the form "short events"
 */
/**@{*/
/** Indicates that a timeout has occurred.  It's not necessary to pass
 * this flag to event_for new()/event_assign() to get a timeout. */
#define EV_TIMEOUT  0x01
/** Wait for a socket or FD to become readable */
#define EV_READ     0x02
/** Wait for a socket or FD to become writeable */
#define EV_WRITE    0x04
/** Wait for a POSIX signal to be raised*/
#define EV_SIGNAL   0x08
/**
 * Persistent event: won't get removed automatically when activated.
 *
 * When a persistent event with a timeout becomes activated, its timeout
 * is reset to 0.
 */
#define EV_PERSIST  0x10
/** Select edge-triggered behavior, if supported by the backend. */
#define EV_ET       0x20
/**@}*/

而event結構中的ev_flags 根據不同的event表項設定的。

/*如果 tv_is_absolute不為0, 則tv表示絕對時間, 而不是間隔差值*/
static inline int
event_add_internal(struct event *ev, const struct timeval *tv,
    int tv_is_absolute)
{
    struct event_base *base = ev->ev_base;
    int res = 0;
    int notify = 0;

    ......
    ......
    /*
     * prepare for timeout insertion further below, if we get a
     * failure on any step, we should not change any state.
     */
    /*如果新新增的事件處理器是定時器,且它尚未被新增到通用定時器佇列或時間堆中,則為該定時器
     * 在時間堆上預留一個位置,如果當前時間堆陣列大小夠了,min_heap_reserve直接返回,如果不夠,resize陣列大小,
     * 保證可以插入新的堆節點
     * ev->ev_flags 為EVLIST_TIMEOUT, 在本函式中通過event_queue_insert(base, ev, EVLIST_TIMEOUT);
     * 如果eve->ev_flags 為EVLIST_TIMEOUT 說明該事件已經在time堆中了*/
    if (tv != NULL && !(ev->ev_flags & EVLIST_TIMEOUT)) {
        if (min_heap_reserve(&base->timeheap,
            1 + min_heap_size(&base->timeheap)) == -1)
            return (-1);  /* ENOMEM == errno */
    }
    ......
    ......
    /*處理沒有啟用的READ WRITE SIGNAL 事件*/
    if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) &&
        !(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) {
        .......
    }

    /*
     * we should change the timeout state only if the previous event
     * addition succeeded.
     */
    /*將事件處理器新增到通用定時器佇列或者事件堆中。
     * res != -1 表示對I/O事件和訊號事件的新增成功,沒有出錯
     * tv != NULl, 表示設定了超時事件*/
    if (res != -1 && tv != NULL) {
        struct timeval now;
        int common_timeout;

        /*
         * for persistent timeout events, we remember the
         * timeout value and re-add the event.
         *
         * If tv_is_absolute, this was already set.
         */
        /*對於persist的時間事件,如果是相對時間引數,用ev_io_timeout記錄這個相對值,
        * 因為每一次的起始時間是不一樣的,需要在不同的起始時間加上相對時間值*/
        if (ev->ev_closure == EV_CLOSURE_PERSIST && !tv_is_absolute) 
            ev->ev_io_timeout = *tv;

        /*
         * we already reserved memory above for the case where we
         * are not replacing an existing timeout.
         */
        /*如果該事件處理器已經被插入通用定時器佇列或時間堆中,則先刪除它*/
        if (ev->ev_flags & EVLIST_TIMEOUT) {
            /* XXX I believe this is needless. */
            if (min_heap_elt_is_top(ev))
                notify = 1;
            event_queue_remove(base, ev, EVLIST_TIMEOUT);
        }

        /* Check if it is active due to a timeout.  Rescheduling
         * this timeout before the callback can be executed
         * removes it from the active list. */
        /*如果待處理的事件已經被啟用,且原因是超時, 則從活動事件佇列中刪除它,
         * 以避免其回撥函式被執行。
         * 對於訊號事件處理器,必要時還需將其ncalls成為設定為0,使得乾淨地終止訊號
         * 事件的處理*/
        if ((ev->ev_flags & EVLIST_ACTIVE) &&
            (ev->ev_res & EV_TIMEOUT)) {
            if (ev->ev_events & EV_SIGNAL) {
                /* See if we are just active executing
                 * this event in a loop
                 */
                if (ev->ev_ncalls && ev->ev_pncalls) {
                    /* Abort loop */
                    *ev->ev_pncalls = 0;
                }
            }

            event_queue_remove(base, ev, EVLIST_ACTIVE);
        }
        /*獲取當前事件now*/
        gettime(base, &now);

        common_timeout = is_common_timeout(tv, base);
        /*計算絕對事件,當前時間加上時間間隔*/
        if (tv_is_absolute) {
            ev->ev_timeout = *tv;
        } else if (common_timeout) {
            struct timeval tmp = *tv;
            tmp.tv_usec &= MICROSECONDS_MASK;
            evutil_timeradd(&now, &tmp, &ev->ev_timeout);
            ev->ev_timeout.tv_usec |=
                (tv->tv_usec & ~MICROSECONDS_MASK);            
       } else {
            evutil_timeradd(&now, tv, &ev->ev_timeout);
        }

        event_debug((
             "event_add: timeout in %d seconds, call %p",
             (int)tv->tv_sec, ev->ev_callback));
        /*將定時器事件新增到通用事件佇列或者最小堆中*/
        event_queue_insert(base, ev, EVLIST_TIMEOUT);
        if (common_timeout) {
            /*如果是通用定時器,並且是尾佇列頭節點時,將ctl結構中的timeout_event事件放入最小堆中*/
            struct common_timeout_list *ctl =
                get_common_timeout_list(base, &ev->ev_timeout);
            if (ev == TAILQ_FIRST(&ctl->events)) {
                common_timeout_schedule(ctl, &now, ev);
            }
        } else {
            /* See if the earliest timeout is now earlier than it
             * was before: if so, we will need to tell the main
             * thread to wake up earlier than it would
             * otherwise. */
            if (min_heap_elt_is_top(ev))
                notify = 1;
        }
    }

    /* if we are not in the right thread, we need to wake up the loop */
    if (res != -1 && notify && EVBASE_NEED_NOTIFY(base))
        evthread_notify_base(base);

    _event_debug_note_add(ev);

    return (res);
}

最小堆 min_heap_reserve/min_heap_size

最小堆實現是用一個數組實現,類似c++ 裡面的vector
一般新增的定時器事件都是最小堆的形式儲存。

struct event_base {
        .....
        truct min_heap timeheap;
        .....
};
typedef struct min_heap
{   
    struct event** p; -------------struct event*結構的陣列
    unsigned n, a;    -------------a:當前分配的總個數;n---當前已經使用的陣列個數
} min_heap_t;

unsigned min_heap_size(min_heap_t* s) { return s->n; }   -----返回當前已使用的堆個數,即陣列個數

int min_heap_reserve(min_heap_t* s, unsigned n)
{
    /* 如果要插入的個數大於堆的總大小, 重新分配堆陣列的個數
     * 如果要插入的個數小於堆的總大小,表示堆陣列有空間可以新的節點*/
    if (s->a < n)
    {
        struct event** p;
        unsigned a = s->a ? s->a * 2 : 8;   /*第一次分配為8, 後續每次重分配的大小為上一次的2倍*/
       /*如果2倍的新空間大小還比n小,設定資料大小為n*/
        if (a < n)
            a = n;
        if (!(p = (struct event**)mm_realloc(s->p, a * sizeof *p)))   -----分配大小為a的struct event *陣列記憶體
            return -1;
        s->p = p;
        s->a = a;
    }
    return 0;
}

通用時間

由於tv_usec是32位元長度, 而表示微秒數只需要20位,

(因為微秒數不能超過999999, 2的20次~= 1048576), 所以用32bits的最後20bits表示微秒數,用最前面的4bits表示magic號,中間8bits表示event_base結構中的通用定時器common_timeout_queues的陣列索引,所以陣列元素最多256個
這裡寫圖片描述

判斷是否為通用時間的方法是:
最高8bit的值為5.
通用時間一般是人為手動新增的。

#define COMMON_TIMEOUT_IDX_MASK 0x0ff00000
#define COMMON_TIMEOUT_IDX_SHIFT 20
#define COMMON_TIMEOUT_MAGIC    0x50000000
#define COMMON_TIMEOUT_MASK     0xf0000000
#define COMMON_TIMEOUT_IDX(tv) \
    (((tv)->tv_usec & COMMON_TIMEOUT_IDX_MASK)>>COMMON_TIMEOUT_IDX_SHIFT)

/** Return true iff if 'tv' is a common timeout in 'base' */
static inline int
is_common_timeout(const struct timeval *tv,  const struct event_base *base)
{   
    int idx;
    if ((tv->tv_usec & COMMON_TIMEOUT_MASK) != COMMON_TIMEOUT_MAGIC)
        return 0;
    idx = COMMON_TIMEOUT_IDX(tv);
    return idx < base->n_common_timeouts;
}

event_base_dispatch/event_base_loop

int
event_base_loop(struct event_base *base, int flags)
{
    const struct eventop *evsel = base->evsel;
    struct timeval tv;
    struct timeval *tv_p;
    int res, done, retval = 0;

    /* Grab the lock.  We will release it inside evsel.dispatch, and again
     * as we invoke user callbacks. */
    EVBASE_ACQUIRE_LOCK(base, th_base_lock);
    /*一個event_base只允許執行一個事件迴圈*/
    if (base->running_loop) {
        event_warnx("%s: reentrant invocation.  Only one event_base_loop"
            " can run on each event_base at once.", __func__);
        EVBASE_RELEASE_LOCK(base, th_base_lock);
        return -1;
    }

    base->running_loop = 1; /*標記該event_base已經開始執行*/

    clear_time_cache(base); /*清除event_base的系統時間快取*/

    if (base->sig.ev_signal_added && base->sig.ev_n_signals_added)
        evsig_set_base(base);

    done = 0;

#ifndef _EVENT_DISABLE_THREAD_SUPPORT
    base->th_owner_id = EVTHREAD_GET_ID();
#endif

    base->event_gotterm = base->event_break = 0;

    while (!done) {
        base->event_continue = 0;
        /*檢視是否需要跳出迴圈, 程式可以呼叫event_loopexit_cb() 設定event_gotterm標記
         * 呼叫event_base_loopbreak()設定event_break 標記*/
        /* Terminate the loop if we have been asked to */
        if (base->event_gotterm) {
            break;
        }

        if (base->event_break) {
            break;
        }
        /*校準系統時間*/
        timeout_correct(base, &tv);

        tv_p = &tv;
        /*base裡面目前啟用的事件數目為0,並且為阻塞性的I/O複用, 則取時間堆上面的最小堆節點的超時時間作為epoll的超時時間*/
        if (!N_ACTIVE_CALLBACKS(base) && !(flags & EVLOOP_NONBLOCK)) {
            timeout_next(base, &tv_p); /*獲取時間堆上堆頂元素的超時值, 即I/O複用系統呼叫本次應該設定的超時值*/
        } else {
            /*
             * if we have active events, we just poll new events
             * without waiting.
             */
            /*如果有就緒事件尚未處理, 則將I/O複用系統呼叫的超時時間"置0"。
             * 這樣I/O複用系統呼叫直接返回, 程式也就可以立即處理就緒事件了*/
            evutil_timerclear(&tv);
        }

        /*如果event_base 中沒有註冊任何事件, 則直接退出事件迴圈*/
        /* If we have no events, we just exit */
        if (!event_haveevents(base) && !N_ACTIVE_CALLBACKS(base)) {
            event_debug(("%s: no events registered.", __func__));
            retval = 1;
            goto done;
        }

        /* update last old time */
        gettime(base, &base->event_tv);  /*更新系統時間*/
        /*之所以要在進入dispatch之前清零,是因為進入  
         *dispatch後,可能會等待一段時間。cache就沒有意義了。  
         *如果第二個執行緒此時想add一個event到這個event_base裡面,在  
         *event_add_internal函式中會呼叫gettime。如果cache不清零,  
         *那麼將會取這個cache時間。這將取一個不準確的時間.*/
        clear_time_cache(base);  /*清除event_base的系統時間快取*/

        /*呼叫事件多路分發器的dispatch方法等待事件, 將就緒事件插入活動事件佇列*/
        res = evsel->dispatch(base, tv_p);

        if (res == -1) {
            event_debug(("%s: dispatch returned unsuccessfully.",
                __func__));
            retval = -1;
            goto done;
        }
        /*將時間快取更新為當前系統時間*/
        update_time_cache(base);

        /* 檢查時間堆上的到期事件並依次執行之 */
        timeout_process(base);

        if (N_ACTIVE_CALLBACKS(base)) {  
            /*呼叫event_process_active 函式依次處理就緒的訊號事件和I/O事件*/
            int n = event_process_active(base);
            if ((flags & EVLOOP_ONCE)
                && N_ACTIVE_CALLBACKS(base) == 0
                && n != 0)
                done = 1;
        } else if (flags & EVLOOP_NONBLOCK)
            done = 1;
    }
    event_debug(("%s: asked to terminate loop.", __func__));

done:
    /*事件迴圈結束, 清空事件快取, 並設定停止迴圈標誌*/
    clear_time_cache(base);
    base->running_loop = 0;

    EVBASE_RELEASE_LOCK(base, th_base_lock);

    return (retval);
}

timeout_next——根據Timer事件計算evsel->dispatch的最大等待時間

static int
timeout_next(struct event_base *base, struct timeval **tv_p)
{
    /* Caller must hold th_base_lock */
    struct timeval now;
    struct event *ev;
    struct timeval *tv = *tv_p;
    int res = 0;
    /*取出最小堆中的最小節點*/
    ev = min_heap_top(&base->timeheap);
    /*節點為空,直接返回 tv_p為NULL, epoll永遠阻塞*/
    if (ev == NULL) {
        /* if no time-based events are active wait for I/O */
        *tv_p = NULL;
        goto out;
    }
    /*獲取當前時間*/
    if (gettime(base, &now) == -1) {
        res = -1;
        goto out;
    }
    /*如果定時器的時間值小於當前時間,表明該定時器值已經過期了,不能使用, epoll永遠阻塞*/
    if (evutil_timercmp(&ev->ev_timeout, &now, <=)) {
        evutil_timerclear(tv);
        goto out;
    }
    /*計算時間的差值, 該差值作為epoll的超時時間*/
    evutil_timersub(&ev->ev_timeout, &now, tv);

    EVUTIL_ASSERT(tv->tv_sec >= 0);
    EVUTIL_ASSERT(tv->tv_usec >= 0);
    event_debug(("timeout_next: in %d seconds", (int)tv->tv_sec));

out:
    return (res);
}

timeout_process—處理超時事件,將超時事件插入到啟用連結串列中

把最小堆中的最小節點的時間作為epoll的超時時間,如果超時了或者有事件發生,都迴圈判斷一下最小堆中的事件是否超時了,如果是,則處理timeout事件

/* Activate every event whose timeout has elapsed. */
static void
timeout_process(struct event_base *base)                                                                                                                                               
{
    /* Caller must hold lock. */
    struct timeval now;
    struct event *ev;
    /*如果時間堆為空,則退出*/
    if (min_heap_empty(&base->timeheap)) {
        return;
    }
    /*獲取當前時間*/
    gettime(base, &now);

    /*迴圈最小堆中的元素, 如果時間已經達到,則將event新增到active佇列中, 並置標記EV_TIMEOUT*/
    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_internal(ev);

        event_debug(("timeout_process: call %p",
             ev->ev_callback));
        event_active_nolock(ev, EV_TIMEOUT, 1);
    }
}

相關推薦

Libevent學習-------定時事件

定時器事件的建立 Libevent 一般呼叫evtimer_new來定義一個定時器事件 #define evtimer_new(b, cb, arg) event_new((b), -1, 0, (cb), (arg)) 從巨集定義來看,

libevent 源碼學習九 —— 集成定時事件

激活 roc 計算 res 最大 沒有 time timeout loop 前言 : 與 Signal 相比,Timer 事件的集成會直觀和簡單很多 1. 集成到事件主循環 因為系統的 I/O 機制都允許程序制定一個最大的等待時間 timeout。就可以根據 T

Qt學習: QTimerEvent定時事件的處理程序代碼示例

軟件 window 編程 重要函數: 1.int startTimer(int); //設置定時器,返回一個ld. 2.int event->timerld(); //返回當前的ld. 3.void killTimer(int); //停止定時器.首先從Qt設計師中拖拽出三個按鈕,由於只是演

多執行緒學習----定時(二)

1.建立一個定時器,執行某個任務,第一次執行在10s鍾以後,之後每隔1s鍾執行一次。 new Timer().schedule(new TimerTask() { @Override public void run() { System.out.println("bom

libevent定時的使用方法

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

libevent timer定時

每隔一秒迴圈執行回撥函式 #include <iostream> #include <event2/event.h> struct cb_arg { struct event *ev; struct timeval tv; }; void timeout_cb

c# Timer event 定時事件

1 簡介 實現定時器事件,每隔一個時間後觸發事件。 2.原理 2.1 名稱空間 using System.Timers; 2.2 宣告定時器 private Timer aTimer; 2.3 例項化定時器並設定屬性 // Create a time

學習定時

S3C2451有 5 個 16 位定時器。其中定時器 0、1、2 和 3 具有脈寬調製(PWM)功能。定時器 4 是一個無輸出引腳的內部定時器。定時器 0 還包含用於大電流驅動的死區發生器。 定時器 0 和 1 共用一個 8 位預分頻器,定時器 2、3 和 4 共用另外的

STM32學習--定時(基本定時

1.功能及原理   基本定時器TIM6和TIM7各包含一個16位自動裝載計數器,由各自的可程式設計預分頻器驅動。它們可以作為通用定時器提供時間基準,特別地可以為數模轉換器(DAC)提供時鐘。實際上,它們在晶片內部直接連線到DAC並通過觸發輸出直接驅動DAC。這

STM32學習--定時(高階定時

高階定時器 1、功能增加 由上圖可瞭解到高階定時器在通用定時器上增加了三個功能部分: ① 時基部分:增加重複次數計數器和RCR暫存器。允許在指定數量的計數週期後產生更新事件,更新相應暫存器,其實際用處暫不瞭解,減少中斷或DMA處理的資源佔用? ②

【js操作dom物件學習筆記五之事件冒泡、location物件、history物件、定時

1.總結addEventListener()和attachEvent()的區別      相同點:都可以為元素繫結事件   不同點:1.方法名不一樣          2.引數的個數不一樣addEventListener三個引數,attachEvent兩個引數        

co_routine.cpp/.h/inner.h(第四部分:定時事件迴圈)—— libco原始碼分析、學習筆記

由於本原始碼蠻長的,所以按照功能劃分模組來分析,分為若干部分,詳見二級目錄↑ 定時器和事件迴圈 libco管理定時事件便是使用時間輪這種資料結構 定時器前驅知識本篇只稍微提一下,具體知識請參考《Linux高效能伺服器程式設計 遊雙 著》第11章,或其他方式學

libevent::事件::定時2

libev ket code val pan reat eat patch socket #define evtimer_new(b, cb, arg) event_new((b), -1, 0, (cb), (arg)) #include &

Windows定時學習

內核對象 arm 三個參數 inf ont 多少 defined win int 定時器是一個在特定時間或者規則間隔被激發的內核對象。結合定時器的異步程序調用可以允許回調函數在任何定時器被激發的時候執行。 通過調用CreateWaitableTimer()可以創建一個定時器

QT學習筆記(14) 定時類DTimer的使用

fin isa play htm number conn stat alt .cn 一、   在前面的學習筆記中,我們已經學習定時器事件http://www.cnblogs.com/blog-ccs/p/7445323.html   現在,我們學習QTimer定時器類,比較

js--定時學習和對動畫的封裝(定時

定時器 回調 .get floor val logs 多個 get math 1.定時器:在js裏面,定時器主要有兩種,setInterval(function, time) 和 setTimeout(function,time), setInterval:每個time秒執

(筆記)Linux內核學習(八)之定時和時間管理

全局變量 define 結構 load 統計 object 一個 完成 溢出 一 內核中的時間觀念 內核在硬件的幫助下計算和管理時間。硬件為內核提供一個系統定時器用以計算流逝的時間。系 統定時器以某種頻率自行觸發,產生時鐘中斷,進入內核時鐘中斷處理程序中進行

python的學習之旅---信號量 定時

def 當前 pre 正在 getname from == and span 把線程都創建好,等待執行。 current_thread().getName() 獲取當前線程的線程名 from threading import Thread,Semaphore,curren

qt學習記錄-----4.qt定時

調用 函數 div 文件 事件處理 post 溢出 mage 分享圖片 兩種開啟定時器方式 一、開啟多個定時器 添加頭文件 設置三個定時器,並設置定時時間 定時器溢出,調用定時器事件處理函數 二、只需少量定時器,采用信號和槽的方式 實現槽函數 qt學習記錄---

java學習筆記之定時

blog div this rgs date row demo sdf 時間 定時器 1 package pack01_timer; 2 3 import java.io.File; 4 import java.text.ParseException; 5 i