定时器

1.定时器的作用:服务器程序管理很多的定时事件,有效的组织这些定时事件,使他们可以在预期的时间点被触发且不影响服务器的主要逻辑,有很重要作用。因此将每个定时事件封装成定时器,使用某种数据结构像链表,排序链表之类的东西将所有定时器连接起来,以实现对定时事件的统一管理。

2.Linux提供三种定时方法:

①socket选项SO_RCVTIMEO和SO_SNDTIMEO

一般用setsockopt函数来设置:其中 optname参数例如为SO_SNDTIMEO代表访问的是发送超时超时选项

定时器_第1张图片

 

eg:struct timeval timeout;   timeout.tv_sec=time;  time.tv_usec=0; socklen_t len=sizeof(timeout);

Set(sockfd,SOL_SOCKET,SO_SNDTIMEO,&timeout,len);

②SIGALRM信号:一般而言SIGALRM信号按照固定的频率生成。如果定时时间不是周期T的整数倍,那么它实际被执行的时间和预期会有偏差

定时器通常要包含两个成员:一个超时时间(相对时间或者绝对时间),一个任务回调函数。

有时还可能包含回调函数被执行时需要传入的参数,以及是否需要重启定时器

如果是链表作为容器来串联所有的定时器,则每个定时器还要包含下一个定时器的指针成员。若为双向链表则需要的是前驱和后继。

在计时器的类中,需要将所有计时器按照时间顺序来有序的排列,比如升序排列就可以从第一个开始逐渐往后顺序触发,当某个定时任务发生变化调动调整函数调整链表中的计时器对象顺序即可。

当SIGALRM信号每次被触发就在其信号处理函数中执行一个tick函数 ,该函数会从头到尾依次处理每个定时器,当判断定时器到期时,就调用定时器的回调函数,执行定时任务,执行完定时任务之后就将该节点从链表中删除,并充值链表头结点,直到遇到一个尚未到期的事件就会退出,等下一次信号触发调用。

tick相当于一个心博函数,每隔一段固定时间就执行一次以检测并处理到期的任务,以检测并处理到期的任务。在该例子中添加定时器时间复杂度为O(n)  删除时间复杂度为O(1)执行任务的时间复杂度也是O(1)

③IO复用系统调用的超时参数

 

两种高效管理定时器的容器:时间轮和时间堆 (定时器容器是一种容器类的数据结构 而定时器则是容器内所容纳的一个对象)

时间轮:采用的是哈希表的思想,轮环上的每个槽指向一条定时器链表,每条链表上的定时器具有相同的特征,他们的定时时间相差:时间轮上的槽数(N)*槽间隔时间(si)的整数倍

要提高时间精度要求si更小  要提高执行效率则要求N值足够大

删除时间复杂度为O(1)执行任务的时间复杂度也是O(1) 添加定时器时间复杂度为O(n)  但是效率比链表要高,因为时间轮将所有定时器散列到不同的链表上,为查找提供了众多的散列表进入接口 ,因此每条链表上的定时器数量较少,更不用说有多个时间轮的的话

 

时间堆:将所有的定时器中超时时间最小的一个定时器的超时值(timeout)作为心跳间隔。这样一旦心跳函数tick被调用,超时时间最小的定时器必然到期,我们就可以在tick函数中处理该定时器。

现在的时间为基准,timeout的值设置为一种相对时间,相对现在的时间,每次都是相对现在timeout最小的定时器被触发。

最小根堆很适合这个

添加一个定时器的时间复杂度是O(lgn) 删除时间复杂度为O(1)执行任务的时间复杂度也是O(1)  因此时间堆的效率很高

你可能感兴趣的:(Linux)