日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

libevent源码学习-----时间管理

發(fā)布時(shí)間:2024/4/19 编程问答 47 豆豆
生活随笔 收集整理的這篇文章主要介紹了 libevent源码学习-----时间管理 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

libevent監(jiān)聽的event有以下幾種

  • 文件描述符/套接字,沒有設(shè)定超時(shí)時(shí)長
  • 信號(hào)
  • 文件描述符/套接字,設(shè)定超時(shí)時(shí)長

對(duì)于時(shí)間,libevent內(nèi)部的時(shí)間管理是通過最小堆實(shí)現(xiàn)的,原因如下

  • 既然某些fd有規(guī)定的超時(shí)時(shí)長,那么io多路復(fù)用函數(shù)就不能永久阻塞,需要設(shè)定一個(gè)超時(shí)時(shí)長(最后一個(gè)參數(shù))
  • 用戶在使用event_add設(shè)定的時(shí)間是相對(duì)于event_add調(diào)用的相對(duì)時(shí)間,這就導(dǎo)致所有具有超時(shí)時(shí)長的event什么時(shí)候超時(shí)是雜亂無章的,沒辦法為io函數(shù)設(shè)定某個(gè)時(shí)長

針對(duì)以上原因,libevent內(nèi)部將所有的event超時(shí)時(shí)長全部轉(zhuǎn)化為絕對(duì)時(shí)間,有以下幾點(diǎn)好處

  • 可以對(duì)所有的超時(shí)時(shí)間進(jìn)行排序,獲得最早超時(shí)的那個(gè)event
  • 判斷是否超時(shí)時(shí)只需和現(xiàn)有時(shí)間比較大小,不需要每次都進(jìn)行相對(duì)時(shí)間的判斷
  • 可以采用最小堆存儲(chǔ)所有具有超時(shí)時(shí)間的event,如果堆頂event未超時(shí),那么所有的event都不會(huì)超時(shí),可以選擇這個(gè)event的超時(shí)時(shí)長最為io函數(shù)的阻塞時(shí)間

使用絕對(duì)時(shí)間帶來的麻煩是如果event是一個(gè)永久事件,那么當(dāng)event被激活后仍然需要重新注冊(cè)到base中,此時(shí)因?yàn)閑vent的時(shí)間是絕對(duì)時(shí)間的緣故,不能夠直接調(diào)用event_add_internal添加event,而是需要重新計(jì)算超時(shí)時(shí)間再添加,這就導(dǎo)致仍然需要在event中存儲(chǔ)用戶提供的超時(shí)時(shí)長,在重新添加之前計(jì)算絕對(duì)時(shí)間的超時(shí)時(shí)間

/** event_add調(diào)用的內(nèi)部函數(shù),用于將event添加到base的注冊(cè)隊(duì)列中* 同時(shí)添加到相應(yīng)的map中* * 注意:這個(gè)函數(shù)不僅僅只由event_add調(diào)用,還有event_persist_closure調(diào)用* 由這個(gè)函數(shù)調(diào)用是因?yàn)楫?dāng)具有超時(shí)時(shí)間的event被激活后,需要先從base中的所有隊(duì)列中刪除* 然后重新計(jì)算超時(shí)時(shí)間,再重新添加到base中,所以又重新調(diào)用了這個(gè)函數(shù)* * 注意:event不僅代表文件描述符,還有可能是信號(hào)的event,當(dāng)是信號(hào)時(shí),會(huì)遞歸* 調(diào)用兩遍這個(gè)函數(shù),第一遍調(diào)用時(shí)判斷是信號(hào)則調(diào)用evsig_map_add函數(shù),在這個(gè)函數(shù)中* 進(jìn)行兩步* 將信號(hào)event添加到base的信號(hào)map中* 調(diào)用evsigops的add函數(shù),即調(diào)用evsig_add,這個(gè)函數(shù)中綁定內(nèi)部信號(hào)處理函數(shù),同時(shí)將socketpair的event* 添加到base中,使用event_add,也就是調(diào)用event_add_internal* 不過只會(huì)執(zhí)行兩遍,因?yàn)樵趀vsig_add中會(huì)進(jìn)行判斷,只有第一次添加socketpair的event時(shí)才會(huì)執(zhí)行第二次調(diào)用* * 見evsig_add*/ 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;EVENT_BASE_ASSERT_LOCKED(base);/* ... *//** prepare for timeout insertion further below, if we get a* failure on any step, we should not change any state.*//** 這一步主要是用來讓最小堆增加一個(gè)位置,并沒有實(shí)際添加到最小堆上* 判斷條件是這是一個(gè)具有超時(shí)時(shí)間的event,同時(shí)在最小堆中沒有這個(gè)event* 這樣就需要在最小堆上留出一個(gè)位置來存放這個(gè)event* 因?yàn)橛脩艨梢詫?duì)同一個(gè)event調(diào)用event_add多次,這就可能兩次event_add除了超時(shí)時(shí)間不同* 其他的都相同,這樣就不需要在留出一個(gè)位置,直接替換以前的就可以* * 如果已經(jīng)在最小堆中,ev_flags將是EVLIST_TIMEOUT*/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 */}/** we should change the timeout state only if the previous event* addition succeeded.*//* 這一步開始處理具有超時(shí)時(shí)間的event */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.*//** event分為永久的和一次的,是用戶在調(diào)用event_new時(shí)傳入的參數(shù)* 對(duì)于永久的event,在被激活一次之后還需要繼續(xù)監(jiān)聽,* 而對(duì)于有超時(shí)時(shí)間的event,需要對(duì)event的超時(shí)時(shí)間進(jìn)行更新* * 為什么:因?yàn)閎ase在進(jìn)行超時(shí)判斷時(shí)是通過絕對(duì)時(shí)間進(jìn)行判斷的,也就是說在添加event的時(shí)候* 將當(dāng)前時(shí)間+時(shí)間間隔獲得的絕對(duì)時(shí)間作為判斷超時(shí)的依據(jù)* 這樣做的原因是不需要在判斷超時(shí)時(shí)比較時(shí)間差,只需要比較當(dāng)前時(shí)間和超時(shí)時(shí)間即可* * 所以,如果event是永久的,那么再處理過一次之后需要更新超時(shí)絕對(duì)時(shí)間,方法就是保存用戶* 傳入的時(shí)間間隔,再下一次添加時(shí)使用** tv_is_absolute是傳入的參數(shù),event_add傳入時(shí)設(shè)為0,表示傳入的時(shí)間是時(shí)間間隔,不是絕對(duì)時(shí)間*/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.*//** 對(duì)于用戶對(duì)同一個(gè)event調(diào)用event_add多次的情況,先將以前的從最小堆* 中刪除,再添加更新的這個(gè)*/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. *//* * 如果此時(shí)event正處于激活隊(duì)列中,從激活隊(duì)列刪了 * 如果是信號(hào),將其發(fā)生次數(shù)設(shè)為0,就不會(huì)調(diào)用信號(hào)處理函數(shù)了*/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);}/* 計(jì)算超時(shí)絕對(duì)事件 */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));/* 調(diào)用event_queue_insert()將具有超時(shí)時(shí)間的event添加到base最小堆中 */event_queue_insert(base, ev, EVLIST_TIMEOUT);/* 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;} return (res); }

這個(gè)函數(shù)被event_add調(diào)用,用于添加所有的event到base中,很明顯函數(shù)內(nèi)部是調(diào)用event_queue_insert添加event的,event_add_internal主要用于分配event,判斷它應(yīng)該添加到base的哪個(gè)地方。
下面是event_queue_insert添加到最小堆的部分

/* 這個(gè)函數(shù)用于將event根據(jù)queue的不同添加到不同的base隊(duì)列中/或者最小堆中 */ static void event_queue_insert(struct event_base *base, struct event *ev, int queue) {EVENT_BASE_ASSERT_LOCKED(base);/** ev_flags作用如上,* 注意是或運(yùn)算,以前的狀態(tài)仍然保留,其實(shí)就是可能同時(shí)存在多個(gè)隊(duì)列*/ev->ev_flags |= queue;/* * 根據(jù)queue的不同值插入到不同的隊(duì)列中* event_add添加event時(shí)為EVLIST_INSERTED,添加到注冊(cè)隊(duì)列中* 激活event時(shí)為EVLIST_ACTIVE,添加到激活隊(duì)列中* 添加具有超時(shí)時(shí)間的event時(shí)為EVLIST_TIMEOUT,添加到最小堆中* 注意,對(duì)于有超時(shí)時(shí)間的event,會(huì)調(diào)用這個(gè)函數(shù)兩次,先注冊(cè)到隊(duì)列,再加入到堆中*/switch (queue) {/* ... */case EVLIST_TIMEOUT: {min_heap_push(&base->timeheap, ev);break;}default:event_errx(1, "%s: unknown queue %x", __func__, queue);} }

上述這些都是為了能夠處理超時(shí)event做鋪墊,現(xiàn)在轉(zhuǎn)到event_base_loop中

/** 實(shí)際的事件驅(qū)動(dòng)循環(huán),其實(shí)就是一個(gè)while循環(huán),每次調(diào)用io復(fù)用函數(shù)進(jìn)行事件監(jiān)聽* 監(jiān)聽返回之前將活躍的event都按優(yōu)先級(jí)添加到base的激活隊(duì)列中* 回到循環(huán)后對(duì)base的激活隊(duì)列中的event按照優(yōu)先級(jí)順序調(diào)用回調(diào)函數(shù)* 再根據(jù)是否是永久event決定要不要從base的所有隊(duì)列中刪除event* 對(duì)于具有超時(shí)時(shí)間的event則需要特殊處理,見timeout_process*/ 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;done = 0;while (!done) {tv_p = &tv;/* * 這一步是用來獲取io復(fù)用函數(shù)的阻塞時(shí)間,此時(shí)有兩種情況* 當(dāng)此時(shí)沒有處于激活狀態(tài)的event,就從最小堆中取得堆頂event的超時(shí)時(shí)間* 如果有已經(jīng)激活的event,則阻塞時(shí)間為0,直接處理,原因可能是因?yàn)槠渌€程激活了某個(gè)時(shí)間造成的* timeout_next函數(shù)取得最小堆堆頂元素的超時(shí)時(shí)間,并與當(dāng)前時(shí)間做差計(jì)算時(shí)間間隔* evutil_timeclear直接清零時(shí)間* 這么取阻塞時(shí)間的原因見下面*/if (!N_ACTIVE_CALLBACKS(base) && !(flags & EVLOOP_NONBLOCK)) {timeout_next(base, &tv_p);} else {/** if we have active events, we just poll new events* without waiting.*/evutil_timerclear(&tv);}/** 調(diào)用Io復(fù)用函數(shù)的監(jiān)聽函數(shù),開始阻塞/非阻塞的監(jiān)聽* 超時(shí)時(shí)間設(shè)置為最小堆中堆頂event的超時(shí)時(shí)間,原因如下* * 此時(shí)監(jiān)聽的有三種event* 第一種是沒有設(shè)置超時(shí)時(shí)間的,包括信號(hào),所以什么時(shí)候返回都不影響* 第二種是取得最小超時(shí)時(shí)間的堆頂event,此時(shí)可以滿足在超時(shí)時(shí)間返回* 第三種是最小堆中的其他event,這些event的超時(shí)時(shí)間在堆頂event之后,因?yàn)槌瑫r(shí)時(shí)間是絕對(duì)時(shí)間* 也就是說如果堆頂event沒有超時(shí),那么其它的event將不可能超時(shí)* 而當(dāng)最小超時(shí)時(shí)間后返回處理超時(shí)之后重新開始監(jiān)聽,* 因?yàn)槭墙^對(duì)時(shí)間,所以不會(huì)影響最小堆的其他event的超時(shí)** 在返回之間,將活躍的event添加到base的激活隊(duì)列中* * 注意:不處理具有超時(shí)時(shí)間的event,因?yàn)檫@些event根本就沒有添加到io函數(shù)中* 處理這些是在timeout_process函數(shù)中*/res = evsel->dispatch(base, tv_p);/** 專門處理超時(shí)的event* 注意為什么可以單獨(dú)處理超時(shí)event,因?yàn)榫哂谐瑫r(shí)時(shí)間的event都被添加到最小堆里面了* 這樣,只需要遍歷最小堆,用堆元素的超時(shí)時(shí)間和當(dāng)前時(shí)間比較,就可以判斷是否超時(shí)* 其實(shí)不需要全部遍歷,而是當(dāng)遇到第一個(gè)沒有超時(shí)的event就可以退出遍歷* 因?yàn)樽钚《训漠?dāng)前節(jié)點(diǎn)永遠(yuǎn)比兩個(gè)子節(jié)點(diǎn)小,所以子節(jié)點(diǎn)的超時(shí)時(shí)間會(huì)更長,不可能超時(shí)* * 需要遍歷而不是只取堆頂?shù)脑蚴窃趶膇o函數(shù)返回到執(zhí)行timeout_process的過程中* 其他線程可能又向最小堆中添加了超時(shí)時(shí)間更小的event,這就導(dǎo)致了事先使用的* 時(shí)間的那個(gè)event已經(jīng)不是堆頂元素了*/timeout_process(base);}return (retval); }

下面是timeout_process函數(shù)

/* * 將最小堆中超時(shí)的event添加到激活隊(duì)列中* 此函數(shù)由event_base_loop調(diào)用*/ 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;}//獲取當(dāng)前時(shí)間,用于判斷是否超時(shí)gettime(base, &now);/* * 需要循環(huán)判斷而不是只取堆頂?shù)脑? 因?yàn)槠渌€程有可能添加了更小的超時(shí)event* 見event_base_loop中timeout_process的調(diào)用*/while ((ev = min_heap_top(&base->timeheap))) {//當(dāng)遇到第一個(gè)沒有超時(shí)的event就可以退出了,原因見base的主循環(huán)if (evutil_timercmp(&ev->ev_timeout, &now, >))break;/* delete this event from the I/O queues *//* * 取出一個(gè)就將這個(gè)event從所有隊(duì)列中刪除* 然后再添加到激活隊(duì)列中* 原因:因?yàn)橐匦掠?jì)算超時(shí)時(shí)間,需要從所有隊(duì)列中刪除*/event_del_internal(ev);event_debug(("timeout_process: call %p",ev->ev_callback));/** 將event添加到超時(shí)隊(duì)列中* 此處需要設(shè)置event被激活的原因,EV_TIMEOUT表示由于超時(shí)被激活*/event_active_nolock(ev, EV_TIMEOUT, 1);} }

總結(jié)
至此就完成了對(duì)具有超時(shí)時(shí)長的event的監(jiān)控和處理,libevent的時(shí)間管理可以學(xué)習(xí)的地方在于將相對(duì)時(shí)間轉(zhuǎn)換成絕對(duì)時(shí)間,不要一根勁,以為用戶提供相對(duì)時(shí)間程序設(shè)計(jì)的時(shí)候就必須使用相對(duì)時(shí)間,反而可以變相思考,進(jìn)行適當(dāng)轉(zhuǎn)換,便于程序設(shè)計(jì)。libevent在轉(zhuǎn)換后既解決了io復(fù)用函數(shù)阻塞時(shí)間問題,又提高了時(shí)間管理的效率(使用最小堆)

總結(jié)

以上是生活随笔為你收集整理的libevent源码学习-----时间管理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。