Timer,时间堆

前言

博主这一段时间都没有更新博客,因为去 写了几个小的项目。接下来的几篇博客就当作是项目的总结吧。其中一个项目就是来自github的C++11版本WebServer。我实现的则放到了gitee上my_webserver

该项目应该也是吸取了最经典的TinyWebServer,主要由以下几个模块组成:

  • 配置模块,一些基础的个性化配置。
  • 日志模块,记录服务器信息,便于查看和修bug。
  • 连接池,项目使用mysql数据库,使用连接池管理mysql连接。
  • 线程池,执行任务。
  • 时间堆,用于管理定时任务。
  • epoller,linux下实现高并发的关键。
  • 缓冲区,用于存储http请求和响应的数据。
  • http模块,主要包括http请求,http响应,和管理http连接。
  • webserver,服务器的主要逻辑。

接下来我会逐一分析markparticle的C++11版本的这些模块。在开始此系列博客之前,强烈推荐大家阅读游双大佬的《Linux高性能服务器编程》,关于这些模块的条条杠杠,不敢说100%,一大半的基础知识都来自这本书。

什么是定时器

服务器中的定时器和我们日常生活中的定时器概念一样,**即将一个事件与一个时间点绑定,时间点一到,就执行该事件。**比如,我想明天8点起床,就定了一个8点的定时器。当闹钟一响,我就会执行起床这个事件。那么,在编程中,时间点很容易表示,如何刻画一个事件呢?没错,就是函数。

void get_up(){}

get_up
这样就能在特定的时间点,执行特定的事件。

当然了,基于不同的任务,我们的时间点设置也会不同,也可能会有半小时以后,2天以后这样的相对时间点,也可能会有绝对时间点。

定时器的实现

服务器往往需要很多个定时任务,这时候就需要一种数据结构管理它们,这就是服务器需要时间管理器的原因。

如果有很多个定时任务,应该怎样管理它们呢?

显然,我们需要基于它们触发的时间先后进行排序,排在前面的事件先触发。而触发的规则又有不同。比如,可以让时间管理器按照一定的周期进行触发,每隔5s触发一次之类的。但是,这样的坏处就是,可能每次触发不一定有事件就绪,白白触发。基于这样一种考虑,我们设法获得下一次触发任务到现在的时间t,然后让时间管理器经过t之后去触发任务,此时至少有一个任务会被触发。

时间堆

作者实现的时间管理器使用的是堆。利用堆的性质,每次可以选取出最值的特点,每次选择时间节点最小的定时器出来执行,以此往复。
而关于时间方面,使用的是C++11引入的chrono库。

typedef std::function<void()> TimeoutCallBack;   //回调函数
typedef std::chrono::high_resolution_clock Clock; //时钟
typedef std::chrono::milliseconds MS;    //毫秒
typedef Clock::time_point TimeStamp;     //时间戳

struct TimerNode{  //时间节点
  int id;
  TimeStamp expires;    //绝对时间
  TimeoutCallBack cb;
  
  bool operator<(const TimerNode& rhs){   //用于比较
    return expires < rhs.expires;
  }
};

这里,先typedef了一些类型用于后续方便使用。也定义了时间结点。你可能会注意到,回调函数的类型是function<void()>,一个没有参数且没有返回值的函数。那么,如果你的定时任务需要参数怎么办?

我们默认回调函数没有返回值。因为回调函数可能带有任意类型的参数,所以干脆将其变成没有参数的,如果你的回调函数带有参数,你需要自己使用bind或者lambda进行封装。这个手法经常使用。

  • id,用于标识一个时间结点,用来调整或者删除。
  • expires,就是定时器触发的时间,这里使用的是时间戳。
  • cb,即时间到之后要执行的任务。

对于时间堆,我们采用数组形式。在插入,调整,删除时间堆时,往往需要获得它们的下标,所以使用哈希存储时间节点id到数组下标的映射关系。

class Timer{
public:
//...
private:
std::vector<TimerNode> heap_;
std::unordered_map<int, int> ref_; //id -> index
};

而对于堆,自然要提供向下调整和向上调整的函数。而为了更好的调整ref_,手写了一个swap函数。

//向下调整法,在[index, n)中调整。不包括n!!!
bool Timer::SiftDown_(int index, size_t n);

//向上调整,原作者的size_t类型的index,可能在这里会有一个bug。
void Timer::SiftUp_(int index);

//交换下标i,j处的值,并调整ref_的映射。
void Timer::swap(int i, int j);
  • 时间管理器肯定要提供一个增加定时器的函数add。该函数会向堆中增加一个定时器,如果该定时器已经存在,那么就更新该定时器。
  • 也提供了调整定时器时间的函数,用新的时间刷新指定定时器。
  • 删除定时器,暴露给外部的接口只会删除堆顶节点。该函数回去调用del_。
//增加定时器,如果定时器已经存在,则用new_expires和cb去更新该定时器。
void add(int id, int new_expires, const TimeoutCallBack& cb);

//调整定时器的触发时间
void adjust(int id, int timeout);

//删除堆顶节点
void pop();

//删除位置为index的节点。私有函数,不会被外部调用。
void del_(int index);
  • 时间堆当然需要工作函数,用来触发到点的时间节点。
  • 需要一个tick心跳函数,去检查时间堆,清除所有过期节点。
  • 需要一个GetNextTick()函数,去获得下一次触发的时间,确保每次触发都会有事件就绪。
  void worker(int id);

  void tick();    //心跳函数

  int GetNextTick();
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值