调整系统时钟导致ACE定时器丢失
由于我们使用的服务器一般依靠纽扣电池作为能源来驱动和记录时钟,通常在运行一段时间后会出现时间误差。所以很多大型的分布式系统都有定时操作,尤其是一些需要精确时钟的分布式系统(比如计费)。往往有一个主机提供精确的时钟服务(可能采用GPS授时)。其他服务器通过本服务器校准时,计时操作一般直接改变系统时钟。
ACE的定时器都是由Event_Handler处理的,Event_Handler一般用绝对时间作为记录超时的时间戳。但是,在调整系统时钟时,绝对时间会造成“遗漏”一些计时器,从而产生一些问题。
设置定时器时,schedule_timer函数通过gettimeofday获取定时器点的时间。
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记录系统时间,
if (0!= this-> timer _ queue _)
return this-> timer _ queue _--> schedule
(handler,
arg,
timer _ queue _--> gettimeofday()+delay _ time,
interval);
}
在调度timer的过程中也会调用gettimeofday函数。
template ACE _ INLINE int
ACE _ Timer _ Queue _ T::expire(void)
{
if(!this-> is _ empty())
return this-> expire(this-> gettimeofday()+timer _ skew _);
else
返回0;
}
可以看出,如果在schedule_timer之后将系统时钟调前(调慢),那么原来的定时器将需要更多的时间来触发。因此计时器在这段时间内不会被触发。从而导致计时器丢失。
这个问题有两个解决方案。简单的方法就是增加系统时钟校准的频率,保证系统时钟的偏差不会影响时钟每次校准的定时器触发。
另一种方法是ACE本身的Timer_Queue提供的。通过上面的代码我们可以发现,其实ACE _ timer _ queue _ t::gettimeofday是一个调用函数指针。使用默认的ACE_OS:: gettimeofday函数,可以替换。
void gettime of day(ACE _ Time _ Value(* gettime of day)(void));
ACE提供了一个高分辨率定时器ACE_High_Res_Timer,该定时器依赖于操作系统。这个类通过OS的滴答数得到一个更精确的时钟[注]。
[注意]OS启动后,总会有一个滴答滴答不停地响。这个勾号就像一个点计数器,每次加1。一般的计数周期是一个CPU周期。
因为CPU的节拍不会随着系统时钟的调整而调整。所以可以看作是一个相对值。ACE_High_Res_Timer可以根据相对值计算出非常精确的程序运行时钟。直接使用ace _ high _ RES _ timer::gettimeofday _ HR函数作为ace _ timer _ queue _ t::gettimeofday函数指针。并且在程序的开头使用了函数ACE _ HIGH _ RES _ TIMER::Global _ Scale _ Factor()来激活高精度定时器。【注意】
【注意】这个方法得益于原公司的两位同事张天虎和廖斌才的解约。在这里,我怀念和他们一起工作的日子。另外,这个方法我还没有仔细研究过,因为CPU获取TICK很可能是内核操作,效率可能不高。
以上两种方法基本可以避免这个问题。
0条评论