怎樣實(shí)現(xiàn)一個(gè)Timer

原文地址:http://whosemario.github.io/2015/11/12/timer/

怎樣實(shí)現(xiàn)一個(gè)Timer(計(jì)時(shí)器)。

1. libuv Timer

之前的文章里面介紹過libuv的Timer使用,在libuv中Timer超時(shí)的判斷是在主循環(huán)中實(shí)現(xiàn)的乾戏。

# in core.c

int uv_run(uv_loop_t* loop, uv_run_mode mode) {
    ... ...
    
    while (r != 0 && loop->stop_flag == 0) {
        uv__update_time(loop);  【1】
        uv__run_timers(loop);   【2】
        ... ...

        timeout = 0;
        if ((mode == UV_RUN_ONCE && !ran_pending) || mode == UV_RUN_DEFAULT)
            timeout = uv_backend_timeout(loop);   【3】

        uv__io_poll(loop, timeout);   
        ... ...
    
}

這里我只列出和Timer相關(guān)的一些邏輯爸邢「盥簦【1】處在每次循環(huán)的開始處宴杀,會(huì)重新獲得當(dāng)前時(shí)間茅郎,并將其賦值給loop->time蜗元。【2】處處理到期的Timer系冗∞瓤郏【3】處會(huì)計(jì)算即將到期的Timer離現(xiàn)在時(shí)間還有多少,此時(shí)間將會(huì)作為poll的超時(shí)時(shí)間掌敬。

2. Libtask

Libtask里面沒有完全意義上的Timer惯豆,這里分析的是taskdelay方法,通過taskdelay方法可以實(shí)現(xiàn)一個(gè)別樣的Timer奔害。

void
timertask(void* v) {
    taskdelay((int)v);  // 延遲v毫秒
    ... ...  // do what you will do
}

taskdelay不是真正意義上的Timer楷兽,但它的實(shí)現(xiàn)卻和Timer的實(shí)現(xiàn)思路很相似,因此向拿出來分析一下华临。先看一下taskdelay的內(nèi)部邏輯芯杀。

uint
taskdelay(uint ms)
{
    uvlong when, now;
    Task *t;

    if(!startedfdtask){
        startedfdtask = 1;
        // fdtask只創(chuàng)建一次
        taskcreate(fdtask, 0, 32768);   【1】
    }

    now = nsec();
    when = now+(uvlong)ms*1000000;
    // 找到第一個(gè)終止時(shí)間大于when的task
    for(t=sleeping.head; t!=nil && t->alarmtime < when; t=t->next)
        ;

    // 將當(dāng)前task插入隊(duì)列      【2】
    if(t){
        taskrunning->prev = t->prev;
        taskrunning->next = t;
    }else{
        taskrunning->prev = sleeping.tail;
        taskrunning->next = nil;
    }

    t = taskrunning;
    t->alarmtime = when;
    if(t->prev)
        t->prev->next = t;
    else
        sleeping.head = t;
    if(t->next)
        t->next->prev = t;
    else
        sleeping.tail = t;

    // 這是做什么?
    if(!t->system && sleepingcounted++ == 0)
        taskcount++;
    taskswitch();   【3】

    return (nsec() - now)/1000000;
}

fdtask后面會(huì)介紹,其實(shí)是調(diào)度的主循環(huán)的task揭厚,如果fdtask沒有創(chuàng)建却特,首先進(jìn)行創(chuàng)建。之后我們會(huì)根據(jù)when將其插入sleeping隊(duì)列中筛圆,sleeping使用列表來維護(hù)核偿,最小堆當(dāng)然是更好的選擇。之后顽染,在【3】處我們進(jìn)行taskswitch漾岳,切換到fdtask。

void
fdtask(void *v)
{
    ... ... 
    for(;;){
        /* let everyone else run */
        // 讓其他的Task先運(yùn)行一下下
        while(taskyield() > 0)      【1】
            ;
        ... ...
        // 計(jì)算poll的超時(shí)時(shí)間
        if((t=sleeping.head) == nil)
            ms = -1;  // 如果sleeping隊(duì)列為空粉寞,poll沒有超時(shí)時(shí)間
        else{
            /* sleep at most 5s */
            now = nsec();
            if(now >= t->alarmtime)
                ms = 0;     // 如果已經(jīng)有超時(shí)timer尼荆,poll立馬返回
            else if(now+5*1000*1000*1000LL >= t->alarmtime)
                ms = (t->alarmtime - now)/1000000;
            else
                ms = 5000;
        }
        // ms 很重要,超時(shí)后有些時(shí)間到期的任務(wù)就可以觸發(fā)起來了
        if(poll(pollfd, npollfd, ms) < 0){
            ... ...
        }

        ... ...
    
        now = nsec();
        // 滿足sleep的任務(wù)可以觸發(fā)了
        while((t=sleeping.head) && now >= t->alarmtime){
            deltask(&sleeping, t);
            if(!t->system && --sleepingcounted == 0)
                taskcount--;
            taskready(t);
        }
    }
}

一開始使用taskyield讓其他在就緒列表的任務(wù)都提前運(yùn)行一下唧垦。之后計(jì)算poll的超時(shí)時(shí)間捅儒。在最后將超時(shí)的timer對(duì)應(yīng)的Task加入就緒列表。是不是和前面處理的Timer邏輯十分相似振亮!

3.mudou

陳碩寫的《Linux多線程服務(wù)端編程》一書中也提到了一種Timer的實(shí)現(xiàn)方法巧还,感覺也是很巧妙的。
他使用timerfd_create(2)/timerfd_settime(2)/timerfd_gettime(2),如下是陳碩對(duì)使用timerfd_*的解釋:

timerfd_create(2)把時(shí)間變成了一個(gè)文件描述符坊秸,該“文件”在定時(shí)器超時(shí)的那一刻變得可讀麸祷,這樣就能很方便地融入select(2)/poll(2)框架中,用統(tǒng)一的方式來處理IO事件和超時(shí)事件褒搔,這也正式Reactor模式的長(zhǎng)處阶牍。

int createTimerfd()
{
    int timerfd = ::timerfd_create(CLOCK_MONOTONIC,
                             TFD_NONBLOCK | TFD_CLOEXEC);
    if (timerfd < 0)
    {
        LOG_SYSFATAL << "Failed in timerfd_create";
    }
    return timerfd;
}

通過timerfd_create創(chuàng)建一個(gè)文件描述符,將其加入到poll中,通過timerfd_settime方法來修改時(shí)間星瘾,但需要注意的是這個(gè)接口只適用于Linux系統(tǒng)下走孽,并不通用。

4.總結(jié)

可以看出Timer的實(shí)現(xiàn)并不是很復(fù)雜琳状,框架會(huì)利用poll(epoll/kqueue)來實(shí)現(xiàn)一個(gè)主tick磕瓷,計(jì)算最先超時(shí)的Timer的時(shí)間作為poll的超時(shí)時(shí)間,當(dāng)poll超時(shí)的時(shí)候會(huì)處理Timer隊(duì)列的每一個(gè)計(jì)時(shí)器的內(nèi)容念逞。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末困食,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子肮柜,更是在濱河造成了極大的恐慌陷舅,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件审洞,死亡現(xiàn)場(chǎng)離奇詭異莱睁,居然都是意外死亡待讳,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門仰剿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來创淡,“玉大人,你說我怎么就攤上這事南吮×詹剩” “怎么了?”我有些...
    開封第一講書人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵部凑,是天一觀的道長(zhǎng)露乏。 經(jīng)常有香客問我,道長(zhǎng)涂邀,這世上最難降的妖魔是什么瘟仿? 我笑而不...
    開封第一講書人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮比勉,結(jié)果婚禮上劳较,老公的妹妹穿的比我還像新娘。我一直安慰自己浩聋,他們只是感情好观蜗,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著衣洁,像睡著了一般墓捻。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上闸与,一...
    開封第一講書人閱讀 49,950評(píng)論 1 291
  • 那天毙替,我揣著相機(jī)與錄音岸售,去河邊找鬼践樱。 笑死,一個(gè)胖子當(dāng)著我的面吹牛凸丸,可吹牛的內(nèi)容都是我干的拷邢。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼屎慢,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼瞭稼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起腻惠,我...
    開封第一講書人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤环肘,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后集灌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體悔雹,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了腌零。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片梯找。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖益涧,靈堂內(nèi)的尸體忽然破棺而出锈锤,到底是詐尸還是另有隱情,我是刑警寧澤闲询,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布久免,位于F島的核電站,受9級(jí)特大地震影響扭弧,放射性物質(zhì)發(fā)生泄漏妄壶。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一寄狼、第九天 我趴在偏房一處隱蔽的房頂上張望丁寄。 院中可真熱鬧,春花似錦泊愧、人聲如沸伊磺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽屑埋。三九已至,卻和暖如春痰滋,著一層夾襖步出監(jiān)牢的瞬間摘能,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工敲街, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留团搞,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓多艇,卻偏偏與公主長(zhǎng)得像逻恐,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子峻黍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理复隆,服務(wù)發(fā)現(xiàn),斷路器姆涩,智...
    卡卡羅2017閱讀 134,637評(píng)論 18 139
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)挽拂、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,066評(píng)論 4 62
  • 前天nodejs發(fā)布了新版本4.0,其中涉及到一個(gè)更新比較多的模塊骨饿,那就是下面要介紹的timer模塊亏栈。 timer...
    淘小杰閱讀 807評(píng)論 1 1
  • 前言從Node.js進(jìn)入人們的視野時(shí)洪鸭,我們所知道的它就由這些關(guān)鍵字組成 事件驅(qū)動(dòng)、非阻塞I/O仑扑、高效览爵、輕量,它在官...
    Www劉閱讀 1,531評(píng)論 0 18
  • 朋友镇饮,在小時(shí)候相交蜓竹,當(dāng)然主要還是脾氣對(duì)味,能處的來储藐。但更多的是受地域俱济,父母社交圈,活動(dòng)范圍钙勃,社會(huì)發(fā)展的限制蛛碌。在一個(gè)...
    芳老頭閱讀 242評(píng)論 0 0