目录
前言
定时器应用
为什么不直接用epoll_wait中的timeout来做定时呢?
因为epoll是会调用内核,多少会有一定的延迟,对于实时性要求很高的任务来说,是不可取的。
定时发送信号
定时器误差大(如上述所说的epoll_wait中设置timeout)该如何处理?
利用定时发送信号来处理,即通过绑定和发送信号的方式,可以立刻进行定时器操作。
定时器数据结构
当有大量定时任务要处理时,需要引入数据结构,用epoll_wait不是最优解。常见的数据结构有红黑树、最小堆和时间轮
设计
需要提供以下API:
1、初始化定时器
2、添加定时任务
3、删除定时任务
4、检测定时任务
定时器的共性
1、能够快速找到节点,进行添加和删除
2、能够快速找到最小节点
红黑树
nginx中的定时器用的就是红黑树
特点
1、用于定时器时:增删时间复杂度为O(logn),查找最小节点的时间复杂度为O(h)
2、中序遍历为有序集合
存在问题与解决方法
问题:若存在多个定时时间相同的任务,红黑树要怎么操作。
解决方法:可以将节点插入到相同节点的右边(中序遍历可以得出:后面加入的定时任务,在后面执行)
最近要执行的定时任务
红黑树最左边的节点
最小堆
最常用的定时器数据结构就是最小堆,libevent、go与libev中用的是最小堆
定义
满二叉树:所有层节点数都是该层所能容纳节点的最大数量
完全二叉树:若二叉树的深度为h,除了h层,其它层的节点数都是该层所能容纳节点的最大数量,且h层所有节点都集中在最左侧
最小堆:
1、是一颗完全二叉树
2、某个节点的值总是小于等于它的子节点的值
3、堆中每个节点的子树都是最小堆
添加和删除
添加:因为只关注父子节点的关系,所以添加的时候,添加到叶节点,然后不断地比较父节点,并判断是否交换
删除:删除某一个节点后,将最右边的叶子节点补上去,然后与最小子节点比较,判断是否需要下降;与父节点比较,判断是否需要上升。
时间复杂度:添加为O(logn),删除为O(1)
特点
1、最小值永远是根节点
2、增删的时间复杂度是O(logn)
3、查找最小节点的复杂度是O(1)
4、相较于红黑树更加稳定,因为是完全二叉树