作者:CppExplore 網(wǎng)址:http://www.shnenglu.com/CppExplore/
一、上篇文章描述。文章《定時器(一)》http://www.shnenglu.com/CppExplore/archive/2008/04/02/46111.html實現(xiàn)了一個定時器模塊,這個實現(xiàn)每次延時時間到都要掃描所有的定時器對象,效率低下。開始設(shè)想的時候,LIST中的定時器對象保存間隔時間段的毫秒值,導(dǎo)致每次延時時間到都要做“時間減少操作”直到減少到零,并且得出不需排序的結(jié)論。
二、改進。如果其中保存超時的精確時間點,而不是保存時間段,則可以在LIST中根據(jù)超時時間點對定時器對象排序,延時時間到,則從鏈表頭掃描定時器對象,取其超時時間點與當(dāng)前時間點對比,如果小于等于當(dāng)前時間點,則進行超時處理,否則終止繼續(xù)掃描,避免不必要的掃描操作。同時插入對象的時候插入到合適的位置,以保持鏈表的順序化。
三、主要數(shù)據(jù)結(jié)構(gòu)。此次容器結(jié)構(gòu)選擇內(nèi)核數(shù)據(jù)結(jié)構(gòu)中的TAILQ,因為LIST沒有插入尾部操作(當(dāng)要插入的定時器對象超時時間點大于所有隊列中的對象的時候)。
四、新的時間類型操作。另一方面很多地方涉及到對struct timeval結(jié)構(gòu)的操作,這里介紹幾個對該結(jié)構(gòu)進行操作的宏,都已經(jīng)在系統(tǒng)頭文件中定義,可以使用函數(shù)原型的方式理解就是如下:
timeradd(struct timeval *p1,struct timeval *p2,struct timeval *result);
timersub(struct timeval *p1,struct timeval *p2,struct timeval *result);
timercmp(struct timeval *p1,struct timeval *p2,operator op);
對struct timespec同樣有timespecadd/timespecsub/timespeccmp,另外還有兩者的轉(zhuǎn)換宏,使用函數(shù)原型的方式理解就是:
TIMEVAL_TO_TIMESPEC(struct timeval *p1,struct timespec *result);
TIMESPEC_TO_TIMEVAL(struct timespec *p1,struct timeval *result);
如果系統(tǒng)的頭文件中找不到,也可以自己實現(xiàn)下,明白這兩個結(jié)構(gòu)的細節(jié)結(jié)構(gòu),實現(xiàn)也很簡單,這里拿timeradd舉例,其它不說了。
#define timeradd(tvp, uvp, vvp) \

do
{ \
(vvp)->tv_sec = (tvp)->tv_sec + (uvp)->tv_sec; \
(vvp)->tv_usec = (tvp)->tv_usec + (uvp)->tv_usec; \

if ((vvp)->tv_usec >= 1000000)
{ \
(vvp)->tv_sec++; \
(vvp)->tv_usec -= 1000000; \
} \
} while (0)
五、實現(xiàn)。和上篇文章相比,改動比較的在add_timer_和process方法。當(dāng)前add_timer_操作需要順序掃描插入到合適的位置以保持鏈表的順序。當(dāng)前process的主要代碼如下:

while(manager->m_state==TIMER_MANAGER_START)
{
tm.tv_sec=manager->m_interval.tv_sec;
tm.tv_usec=manager->m_interval.tv_usec;
while(select(0,0,0,0,&tm)<0&&errno==EINTR);
gettimeofday(&now,0);

/**//*加上誤差補償時間*/
timeradd(&now,&manager->m_repair,&stand);
pthread_mutex_lock(&manager->m_mutex);

TAILQ_FOREACH(item, &(manager->list_), entry_)
{

/**//*取修正后的時間逐個和定時器中的超時時間點相比,遇到不超時對象則退出掃描*/

if(timercmp(&item->m_endtime,&stand,<))
{
if(item->m_func)
item->m_func(item,item->m_data);

if(item->m_type==CTimer::TIMER_ONCE)
{
manager->remove_timer_(item);
item->m_state=CTimer::TIMER_TIMEOUT;
}

else if(item->m_type==CTimer::TIMER_CIRCLE)
{

/**//*循環(huán)性的要保證鏈表的順序性,如要重新插入,保存entry_的原因,是執(zhí)行新的插入后不要影響當(dāng)前進行的循環(huán)*/
tmpTimer.entry_=item->entry_;
manager->remove_timer_(item);
manager->add_timer_(item);
item=&tmpTimer;}
}
else break;
}
pthread_mutex_unlock(&manager->m_mutex);
}
六、源碼寫了個簡單的makefile,執(zhí)行make就可以生成測試程序:test,執(zhí)行./test可以看運行效果。make由make so和make test組成,make so在lib目錄下生成libtimer.a。make clean清空。
源代碼下載
這里:
http://www.shnenglu.com/Files/CppExplore/timer.tar.gz [make so有問題,貌似so是關(guān)鍵字,下載后把so改下,隨意什么單詞。]
七、后記與上一版本相比,該文中的定時器實現(xiàn)要求在定時器模塊運行期間不能修改系統(tǒng)實際,上一版本實現(xiàn)則無此限制。
定時器模塊的鎖初始化為fastmutex方式,因此在回調(diào)函數(shù)里注意不能再調(diào)用CTimer的start stop reset等方法,以免死鎖,如果有調(diào)用的需求,可以把鎖修改為循環(huán)鎖recmutex方式。
2008/4/8補記:本文是timer的v2實現(xiàn),定時器timer在業(yè)務(wù)線程中執(zhí)行start的時候,要執(zhí)行掃描排序操作,導(dǎo)致返回時間延長。后續(xù)修改:
(1)定時器timer的start操作不再執(zhí)行掃描操作,而是簡單插入隊列頭同時置一變量表示尚未排序。定時器線程延遲時間到,首先掃描未排序?qū)ο螅瑘?zhí)行排序(一次性timer從尾部掃描對比,循環(huán)性從頭部掃描對比),再掃描判斷是否超時。
(2)func移動到鎖外執(zhí)行。
針對windows客戶端棧使用定時器不多,并且定時間隔不能受系統(tǒng)時間影響(客戶端的系統(tǒng)時間可能被修改),仍然使用v1的實現(xiàn)。