定时器Timer

目录

一、定时器应用

二、定时器设计

1. 接口设计

2. 实现方式

2.1 红黑树

2.2 最小堆

2.3 时间轮

源码





一、定时器应用

  • 心跳检测/周期任务
  • 倒计时/超时机制

对于服务端而言,驱动服务端的事件主要有两种:网络事件、定时事件。

不同的框架中,这两种事件有不同的实现方式:

第一种:网络事件和时间事件在一个线程中配合使用。如:Nginx、Redis;

第二种:网络事件和时间事件在不同线程中处理。如skynet。

// 第一种:
while (!quit) {
    time_t now = time(NULL);
    int timeout = get_nearest_timer() - now;
    if (timeout < 0) timeout = 0;
    int nEvent = epoll_wait(epfd, ev, nev, timeout);
    for (int i = 0; i < nEvent; i++) {
        // 处理网络事件
    }
    update_timer();  // 处理时间事件
}
// 第二种
void *thread_timer(void *p) {
    init_timer();
    while (!quit) {
        update_timer();  // 更新定时器,并处理时间事件或把时间事件发送到MQ
        sleep(t);        // t要小于时间精度
    }
    clear_timer();
    return NULL;
}


二、定时器设计


1. 接口设计

// 初始化timer
void init_timer();

// 添加timer
Node *add_timer(int expire, callback cb);

// 删除timer
bool del_timer(Node *node);

// 找到最近的定时任务
Node *find_nearest_timer();

// 更新timer
void update_timer();

// 清除timer
void clear_timer();

2. 实现方式

  • 红黑树
  • 最小堆
  • 跳表
  • 时间轮

待确认

实现方式增删查查找最小节点应用场景
RBTreeO(log2^N)O(log2N)Nginx Redis
MinHeap

增查:O(log2N)

删:O(N) ,可通过辅助数据结构(hash table)快速定位节点,加快删除操作

O(1)常用?
SkipList

O(log2N)

空间复杂度O(1.5N)

O(1)
时间轮O(1)O(1)Linux内核

2.1 红黑树

这里需要和STL的map区分,因为这里可能存在相同的key,而map的key是唯一的。可以考虑使用multimap,也可以自己实现。

nginx中的实现如下:

void ngx_rbtree_insert_timer_value(ngx_rbtree_node_t *temp,
    ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
{
    ngx_rbtree_node_t **p;
    for ( ;; ) {
        // 注意:key相同时,p选择right
        p = ((ngx_rbtree_key_int_t) (node->key - temp->key) < 0)
            ? &temp->left : &temp->right;
        if (*p == sentinel) {
            break;
        }
        temp = *p;
    }
    *p = node;
    node->parent = temp;
    node->left = sentinel;
    node->right = sentinel;
    ngx_rbt_red(node);
}

2.2 最小堆

完全⼆叉树:若⼆叉树的深度为 h ,除了 h 层外,其他层的节点数都是该层所能容纳节点的最⼤
数量(满⾜2^n),且 h 层都集中在最左侧;

最小堆特性:
  1. 是⼀颗完全⼆叉树;
  2. 某⼀个节点的值总是⼩于等于它的⼦节点的值;
  3. 堆中每个节点的⼦树都是最⼩堆;

增加节点步骤:

        为了满足完全二叉树特性,把新增的节点放到最后的位置,然后考虑是否需要上升。如添加“4”,4为5的左子树,4<5 需要上升。

删除节点步骤:

       删除前需要查询节点是否存在,最小堆的查询效率是O(n),查询到节点后,把该节点和最后一个节点交换位置,先考虑下降操作,如果操作失败则考虑上升操作,最后删除最后一个节点

如删除“1”,首先1和3交换,3和2交换,再删除末尾节点;

如删除“9“,首先9和3交换,3不能下沉了,考虑上升操作,3和7交换。

2.3 时间轮

单层时间轮

如何实现:客户端每 5 秒钟发送⼼跳包;服务端若 10 秒内没收到⼼跳数据,则清除连接;

实际在开发过程中,若收到除了⼼跳包的其他数据,⼼跳检测也算通过,在这⾥为了简化流程,只
判断⼼跳包;


作为对⽐:我们假设使⽤ map<int, conn*> 来存储所有连接数;每秒检测 map 结构,那么每秒需
要遍历所有的连接,如果这个map结构包含⼏万条连接,那么我们做了很多⽆效检测;考虑极端
情况,刚添加进来的连接,下⼀秒就需要去检测,实际上只需要10秒后检测就⾏了;那么我们考
虑使⽤时间轮来检测;


注意:这个例⼦只是⽤来帮助理解时间轮,不代表实际解决⽅案;

单层时间轮如下图,需要注意的地方:

   a. 在同一个节点可以支持多个定时任务,可采用链表链接;

   b. 时间轮长度最好采用2^n,则 m%2^n 可以转化为 m&(2^n-1)

   c. 每次收到心跳包时 used++,每次执行定时任务时used--,当used==0时,心跳超时。

多层时间轮

参考时钟的运行。

假设层级1每10ms运行一次定时任务;

当层级1运行完255的定时任务,此时层级2移动一格:将该格的定时任务重新映射到层级1(注意,层级2的一格,可以表示层级1的255个格);以此类推,层级2运行完63格的定时任务,层级3移动一格,并将层级3的这一格映射到层级2

以下引用于:一张图理解Kafka时间轮(TimingWheel),看不懂算我输! - 知乎

下图更好理解多层时间轮,图中展示了每个时间格对应的过期时间范围, 我们可以清晰地看到, 第二层时间轮的第0个时间格的过期时间范围是 [0,19]。也就是说, 第二层时间轮的一个时间格就可以表示第一层时间轮的所有(20个)时间格;


  


源码

代码:0voice/7.1 timers at main · T-Mac1991/0voice · GitHub

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux 定时器 timer_list 是一个内核数据结构,用于管理内核中的定时器。它是一个双向链表,每个节点表示一个定时器timer_list 的定义位于 `<linux/timer.h>` 头文件中。 每个 timer_list 节点的定义如下: ```c struct timer_list { struct list_head entry; // 定时器节点的链表指针 unsigned long expires; // 定时器的到期时间 void (*function)(unsigned long); // 定时器回调函数 unsigned long data; // 传递给回调函数的参数 struct tvec_base *base; // 定时器所属的时间轮 int slack; // 定时器的松弛时间 }; ``` 其中,`entry` 是一个 `list_head` 结构,用于将节点连接到定时器链表中。`expires` 表示定时器的到期时间,以 jiffies 单位表示。`function` 是定时器的回调函数,在定时器到期时被调用。`data` 是传递给回调函数的参数。`base` 表示定时器所属的时间轮,`slack` 是定时器的松弛时间,用于处理定时器的精度。 在使用 timer_list 时,可以使用以下函数进行初始化和操作: - `timer_setup(struct timer_list *timer, void (*function)(unsigned long), unsigned int flags)`:初始化一个定时器,并指定回调函数和标志。 - `init_timer(struct timer_list *timer)`:初始化一个定时器。 - `add_timer(struct timer_list *timer)`:将定时器添加到定时器链表中。 - `del_timer(struct timer_list *timer)`:从定时器链表中删除定时器。 - `mod_timer(struct timer_list *timer, unsigned long expires)`:修改定时器的到期时间。 这些函数可以通过 `<linux/timer.h>` 头文件中的宏来调用。通过操作 timer_list,可以实现在 Linux 内核中的定时器功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值