定时器详解(定时器链表、时间轮、时间堆)

本文介绍了Linux系统中处理定时事件的三种方法:高性能定时器、时间堆和基于升序链表的定时器。详细讲解了它们的工作原理,包括时间轮的散列思想和时间堆的高效特性,旨在实现精确且高效的定时任务管理。
摘要由CSDN通过智能技术生成

定时器(linux)

网络程序需要处理定时事件,比如定期检测一个客户连接的活动状态。服务器通常管理着众多定时事件,因此有效的组织这些定时事件,使之能在预期的时间点被触发且不影响服务器的主要逻辑,对于服务器性能有着至关重要的影响。为此我们要将每个定时事件分别封装成定时器,并使用某种容器类数据结构,比如链表、排序链表和时间轮,将所有定时器串联起来,以实现对定时事件的统一管理。

LINUX提供了三种定时方法
  • socket选项SO_RCVTIMEO和SO_SNDTIMEO
    在这里插入图片描述
    由表可见,我们可以根据系统调用send,recv,accept,connect的返回值和errno来判断超时时间是否已到,进而决定是否开始处理定时任务。下列代码以connect为例,说明程序如何使用SO_SNDTIMEO选项来定时
/*设置超时时间*/

#ifndef _HEAD_H
#define _HEAD_H
#include<iostream>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<unistd.h>
#include<string.h>
#include<netdb.h>
#include<sys/epoll.h>
#include<pthread.h>

void my_err(std::string str, int line)
{
   
    fprintf(stderr, "line:  %d\n", line);
    std::cerr << str << std::endl;
    exit(0);
}

#endif



#include"head.hpp"
#include<errno.h>

/*超时连接函数*/
int timeout_connect(const char *ip, int port, int time)
{
   
    int ret = 0;
    struct sockaddr_in serv;
    memset(&serv, 0, sizeof(serv));
    serv.sin_family = AF_INET;
    serv.sin_port = htons(port);
    inet_pton(AF_INET, ip, &serv.sin_addr);

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);


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

    /*通过选项SO_RCVTIMEO和SO_SNDTIMEO所设置的超时时间的类型是timeval, 这和select系统调用的超时参数类型相同*/
    ret = setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, len);

    ret = connect(sockfd, (struct sockaddr*)&serv, sizeof(serv));
    if(ret == -1)
    {
   
        /*超时对应的错误号是EINPROGRESS。下面这个条件如果成立,我们就可以处理定时任务了*/
        if(errno == EINPROGRESS)
        {
   
            printf("connecting timeout, process timeout logic\n");
            return -1;
        }
        printf("error occur when connecting to server\n");
        return -1;
    }
    return sockfd;
}
  • SIGALRM
    我们由alarm和setitimer函数设置的定时闹钟一旦超时,将触发SIGALRM信号。因此,我们可以利用该信号的信号处理函数来处理定时任务。但是,如果要处理多个定时任务,我们就需要不断的触发SIGALRM信号,并在其信号处理函数中执行到期的任务。一般而言,SIGALRM信号按照固定的频率生成,既由alarm和setitimer函数设置的定时周期T保持不变。如果某个定时任务的超时时间不是T的整数倍,那么它实际被执行的事件和预期的时间略有偏差。因此定时周期T反映了定时的精度。
    我们将实现一个简单地定时器——基于升序链表的定时器。
    基于升序链表的定时器
    定时器通常包含两个成员:一个超时时间(相对时间或者绝对时间)和一个任务回调函数。
    下面是代码的实现linux环境下
#ifndef LST_TIMER
#define LST_TIMER
#include"head.hpp"
#include<time.h>

#define BUFFER_SIZE 64
class util_timer;  

/*用户数据结构: 客户端socket地址 文件描述符 读缓存和定时器*/
struct client_data
{
   
    struct sockaddr_in address;
    int sockfd;
    char buf[BUFFER_SIZE];
    util_timer* timer;
};
/*定时器类*/
class util_timer
{
   
public: 
    util_timer() : prev(NULL), next(NULL) {
   }

public: 
    time_t expier; //任务的超时时间,这里使用绝对时间
    void (*cb_func)(client_data*); //任务回调函数
    client_data *user_data;
    util_timer* prev; //指向前一个定时器
    util_timer* next; //指向后一个定时器

};

/*定时器链表。 它是一个升序,双向链表且带有头结点和尾节点*/
class sort_timer_lst
{
   
public: 
    sort_timer_lst() : head(NULL), tail(NULL) {
   }
    /*链表被销毁时,删除其中所有的定时器*/
    ~sort_timer_lst()
    {
   
        util_timer* tmp = head;
        while(tmp)
        {
   
            head = tmp->next;
            delete tmp;
            tmp = head;
        }
    }
    /*将目标定时器timer添加到链表中*/
    void add_timer(util_timer* timer);

    /*当某个定时器的任务发生变化时,调整对应的定时器在链表中的位置。*/
    void adjust_timer(util_timer* timer);

    /*将目标定时器timer从链表中移除*/
    void del_timer(util_timer* timer);

    /*SIGALRM信号每次被触发就在其信号处理函数(如果使用统一事件源,则是主函数)中执行一次tick函数,以处理链表上到期的任务*/
    void tick();

    /*重载的add_timer函数,它被公有的add_timer函数和adjust_timer的函数调用。
    该函数表示将目标定时器timer添加到节点lst_head之后的节点lst_head之后的部分链表中*/
    void add_timer(util_timer* timer, util_timer* lst_head);


public: 
    util_timer* head;
    util_timer* tail;
};


/*将目标定时器timer添加到链表中*/
void sort_timer_lst::add_timer(util_timer* timer)
{
   
    if(!timer)
    {
   
        return;
    }
    if(!head) //如果链表中无节点
    {
   
        head = tail = timer;
        return;
    }
    /*如果目标定时器的超时时间小于当前链表中所有定时器的超时时间,则把该节点插入到链表头节点。
     否则就需要调用重载函数add_timer(util_timer* timer, util_timer* lst_head), 把它插入链表中合适的位置,以保证链表的升序特性*/

    if(timer->expier < head->expier)
    {
   
        timer->next = head;
        head = timer;
        head = timer;
        return;
    }
    add_timer(timer, head);
}

/*当某个定时器的任务发生变化时,调整对应的定时器在链表中的位置。*/
void sort_timer_lst::adjust_timer(util_timer* timer)
{
   
    if(!timer)
        return;
    /*当需要调整时,我们只需将此节点从链表中取出,然后重新插入*/
  //  util_timer* prev = timer->prev;
  //  util_timer* next = timer->next;
    timer->prev->next = timer->next;
    timer->next->prev = timer->prev;
    timer->prev = timer->next = NULL;
    add_timer(timer);
}


/*将目标定时器timer从链表中移除*/
void sort_timer_lst::del_timer(util_timer* timer)
{
   
    if(!timer)
        return;
    /*下面这个条件成立表示链表只有一个定时器,既目标定时器*/
    if( (timer == head) && (timer == tail) )
    {
   
        delete timer;
        head = tail = nullptr;
        return;
    }
    /*如果目标至少存在两个定时器,且目标定时器是链表的头结点,则将链表的头结点重置为原头结点的下一个节点,然后删除目标定时器*/
    if(timer == head)
    {
   
        head = timer->next;
        head->prev = nullptr;
        delete timer;
        return;
    
  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值