由于我們采用的服務(wù)器一般都是靠紐扣電池作為能源驅(qū)動和記錄時鐘,一般在運(yùn)行一段時間后都會出現(xiàn)時間誤差。所以很多大規(guī)模的分布系統(tǒng)都有校時操作,特別是一些對時鐘要求精確的分布式系統(tǒng)(比如計費(fèi)等),往往都會有一個主機(jī)提供精確時鐘服務(wù)(其可能采用GPS校時),其他服務(wù)器通過這臺服務(wù)器校時,校時操作一般都是直接改變系統(tǒng)時鐘。
ACE的定時器都是采用Event_Handler進(jìn)行處理,而Event_Handler一般而言都是采用絕對時間作為記錄超時的時間戳,但是絕對時間的方式在系統(tǒng)時鐘被調(diào)整的時候,會導(dǎo)致“丟失”部分定時器的處理,導(dǎo)致一些問題。
在設(shè)置定時器時,schedule_timer函數(shù)通過gettimeofday得到定時器時間點(diǎn)的時間。
template long
ACE_Select_Reactor_T::schedule_timer
(ACE_Event_Handler *handler,
const void *arg,
const ACE_Time_Value &delay_time,
const ACE_Time_Value &interval)
{
// schedule_timer記錄的是系統(tǒng)時間,
if (0 != this->timer_queue_)
return this->timer_queue_->schedule
(handler,
arg,
timer_queue_->gettimeofday () + delay_time,
interval);
}
在派發(fā)定時器的過程中也是調(diào)用gettimeofday函數(shù)。
template ACE_INLINE int
ACE_Timer_Queue_T::expire (void)
{
if (!this->is_empty ())
return this->expire (this->gettimeofday () + timer_skew_);
else
return 0;
}
可以看出,如果在schedule_timer后,將系統(tǒng)時鐘向前調(diào)節(jié)(調(diào)慢)以后,原有的定時器將要經(jīng)過更多的時間才能觸發(fā)。從而導(dǎo)致這段時間內(nèi)定時器無法觸發(fā)。從而造成定時器丟失。
這個問題的解決方法有2個,簡單方法是將系統(tǒng)時鐘校準(zhǔn)的頻度提高,保證每次校準(zhǔn)的時候,系統(tǒng)的時鐘出現(xiàn)的偏差都不會影響時鐘的定時器觸發(fā)。
另外一種是ACE的Timer_Queue自己提供的方法,通過上面的代碼我們可以發(fā)現(xiàn),其實ACE_Timer_Queue_T::gettimeofday是一個調(diào)用的是一個函數(shù)指針。默認(rèn)使用ACE_OS:: gettimeofday函數(shù),這個函數(shù)可以替換的。
void gettimeofday (ACE_Time_Value (*gettimeofday)(void));
ACE提供一個依賴于操作系統(tǒng)的高解析定時器,ACE_High_Res_Timer,這個類是通過OS的TICK數(shù)量來得到更加精確的時鐘的【注】。
【注】OS在啟動后,都會有一個TICK在不斷的計數(shù),這個TICK就像一個打點(diǎn)計數(shù)器,每次增加1.一般計數(shù)周期就是一個CPU周期。
由于CPU的TICK不會隨著你調(diào)整系統(tǒng)時鐘而調(diào)整。所以可以看做是一個相對值。ACE_High_Res_Timer可以根據(jù)相對值計算得到非常精確的程序運(yùn)行時鐘,。直接使用ACE_High_Res_Timer:: gettimeofday_hr函數(shù)作為ACE_Timer_Queue_T::gettimeofday函數(shù)指針。并且在程序的開始部分使用函數(shù),ACE_High_Res_Timer::global_scale_factor (),用于激活高精度定時器。【注】
【注】這個方法得益于原來公司的兩位同事zhangtianhu和liaobincai的一個終結(jié)。在此懷念一下和他們共事的日子。另外,我沒有仔細(xì)研究過這個方法,由于獲取CPU的TICK的獲取很有可能是一個內(nèi)核操作,效率可能不高。
采用上述的兩個方法基本可以避免這個問題。
ACE的定時器都是采用Event_Handler進(jìn)行處理,而Event_Handler一般而言都是采用絕對時間作為記錄超時的時間戳,但是絕對時間的方式在系統(tǒng)時鐘被調(diào)整的時候,會導(dǎo)致“丟失”部分定時器的處理,導(dǎo)致一些問題。
在設(shè)置定時器時,schedule_timer函數(shù)通過gettimeofday得到定時器時間點(diǎn)的時間。
template
ACE_Select_Reactor_T::schedule_timer
(ACE_Event_Handler *handler,
const void *arg,
const ACE_Time_Value &delay_time,
const ACE_Time_Value &interval)
{
// schedule_timer記錄的是系統(tǒng)時間,
if (0 != this->timer_queue_)
return this->timer_queue_->schedule
(handler,
arg,
timer_queue_->gettimeofday () + delay_time,
interval);
}
在派發(fā)定時器的過程中也是調(diào)用gettimeofday函數(shù)。
template
ACE_Timer_Queue_T
{
if (!this->is_empty ())
return this->expire (this->gettimeofday () + timer_skew_);
else
return 0;
}
可以看出,如果在schedule_timer后,將系統(tǒng)時鐘向前調(diào)節(jié)(調(diào)慢)以后,原有的定時器將要經(jīng)過更多的時間才能觸發(fā)。從而導(dǎo)致這段時間內(nèi)定時器無法觸發(fā)。從而造成定時器丟失。
這個問題的解決方法有2個,簡單方法是將系統(tǒng)時鐘校準(zhǔn)的頻度提高,保證每次校準(zhǔn)的時候,系統(tǒng)的時鐘出現(xiàn)的偏差都不會影響時鐘的定時器觸發(fā)。
另外一種是ACE的Timer_Queue自己提供的方法,通過上面的代碼我們可以發(fā)現(xiàn),其實ACE_Timer_Queue_T::gettimeofday是一個調(diào)用的是一個函數(shù)指針。默認(rèn)使用ACE_OS:: gettimeofday函數(shù),這個函數(shù)可以替換的。
void gettimeofday (ACE_Time_Value (*gettimeofday)(void));
ACE提供一個依賴于操作系統(tǒng)的高解析定時器,ACE_High_Res_Timer,這個類是通過OS的TICK數(shù)量來得到更加精確的時鐘的【注】。
【注】OS在啟動后,都會有一個TICK在不斷的計數(shù),這個TICK就像一個打點(diǎn)計數(shù)器,每次增加1.一般計數(shù)周期就是一個CPU周期。
由于CPU的TICK不會隨著你調(diào)整系統(tǒng)時鐘而調(diào)整。所以可以看做是一個相對值。ACE_High_Res_Timer可以根據(jù)相對值計算得到非常精確的程序運(yùn)行時鐘,。直接使用ACE_High_Res_Timer:: gettimeofday_hr函數(shù)作為ACE_Timer_Queue_T::gettimeofday函數(shù)指針。并且在程序的開始部分使用函數(shù),ACE_High_Res_Timer::global_scale_factor (),用于激活高精度定時器。【注】
【注】這個方法得益于原來公司的兩位同事zhangtianhu和liaobincai的一個終結(jié)。在此懷念一下和他們共事的日子。另外,我沒有仔細(xì)研究過這個方法,由于獲取CPU的TICK的獲取很有可能是一個內(nèi)核操作,效率可能不高。
采用上述的兩個方法基本可以避免這個問題。