目录
1. 定时器触发方式
1.1 网络事件和定时事件在一个线程中处理
例如:nginx、redis、memcached;
定时器通常是与网络组件一起工作,⽹络事件和时间事件在⼀个线程当中配合使⽤;例如nginx、redis,我们将epoll_wait的第四个参数timeout设置为最近要触发的定时器的时间差来触发定时器,来执行任务。
// 网络事件和定时事件在一个线程中处理
while (!quit) {
int timeout = get_nearest_timer() - now();
if (timeout < 0) timeout = -1;
int nevent = epoll_wait(epfd, ev, nev,timeout);
for (int i=0; i<nevent; i++) {
// ... 处理网络事件
}
// 处理定时事件
update_timer();
}
1.2 网络事件和定时事件在不同线程中处理;
例如:skynet,...;在单独的线程来检测定时器。通过usleep来触发定时器,定时任务的执行通过信号或者插入执行队列让其他线程执行。
// 网络事件和定时事件在不同线程中处理
void * thread_timer(void *thread_param) {
init_timer();
while (!quit) {
update_timer();
sleep(t);
}
clear_timer();
return NULL;
}
pthread_create(&pid, NULL, thread_timer,&thread_param);
2. 定时器设计
2.1 接口设计
// 初始化定时器
void init_timer();
// 添加定时器
Node* add_timer(int expire, callback cb);
// 删除定时器
bool del_timer(Node* node);
// 找到最近要触发的定时任务
Node* find_nearest_timer();
// 更新检测定时器
void update_timer();
// 清除定时器
// void clear_timer();
2.2 数据结构设计
对定时任务的组织本质是要处理对定时任务优先级的处理;由此产生两类数据结构;
1.按触发时间进行顺序组织
要求数据结构有序(红黑树、跳表),或者相对有序(最小堆);
能快速查找最近触发的定时任务;
需要考虑怎么处理相同时间触发的定时任务;
2.按执行顺序进行组织
时间轮
2.3 整体思路
选择set容器(红黑树实现)
2.3.1 获取当前时间的接口
//获取当前时间
//定义静态成员,类共享
static time_t GetTick() {
//chrono是c++ 11中的时间库,提供计时,时钟等功能
//毫秒:std::chrono::milliseconds
//time_point_cast对时间点进行转换
//chrono::steady_clock进行程序耗时的时长,只要启动就会进行时间的累加,并且不能被修改,非常适合于进行耗时的统计。
auto sc = chrono::time_point_cast<chrono::milliseconds>(chrono::steady_clock::now());
//time_since_epoch 获取对象经过的时间间隔
auto temp = chrono::duration_cast<chrono::milliseconds>(sc.time_since_epoch());
return temp.count();
}
2.3.2 相同触发时间的定时任务处理
2.3.3 完整代码
#include <sys/epoll.h>
#include <functional>
#include <chrono>
#include <set>
#include <memory>
#include <iostream>
using namespace std;
struct TimerNodeBase {
time_t expire;
int64_t id;
};
//公有继承
struct TimerNode : public TimerNodeBase {
//std::function是一个可调用对象包装器,是一个类模板
//相当于 typedef std::function<void(const TimerNode &node)> Callback;
using Callback = std::function<void(const TimerNode &node)>;
Callback func;
//结构体定义构造函数
//成员func = 形参func
TimerNode(int64_t id, time_t expire, Callback func) : func(func) {
//形参名与成员变量名相同,则用{this->成员变量名=形参名;}
this->expire = expire;
this->id = id;
}
};
//运算符重载
bool operator < (const TimerNodeBase &lhd, const TimerNodeBase &rhd) {
if (lhd.expire < rhd.expire)
return true;
else if (lhd.expire > rhd.expire)
return false;
return lhd.id < rhd.id;
}
class Timer {
public:
static time_t GetTick() {
//chrono是c++ 11中的时间库,提供计时,时钟等功能。
//毫秒:std::chrono::milliseconds
//time_point_cast对时间点进行转换
//chrono::steady_clock进行程序耗时的时长,只要启动就会进行时间的累加,并且不能被修改,非常适合于进行耗时的统计。
auto sc = chrono::time_point_cast<chrono::milliseconds>(chrono::steady_clock::now());
//time_since_epoch 获取对象经过的时间间隔
auto temp = chrono::duration_cast<chrono::milliseconds>(sc.time_since_epoch());
return temp.count();
}
//添加
TimerNodeBase AddTimer(time_t msec, TimerNode::Callback func) {
time_t expire = GetTick() + msec;
//emplace() 返回的迭代器指向新插入的元素
auto ele = timermap.emplace(GenID(), expire, func);
return static_cast<TimerNodeBase>(*ele.first);
}
//移除
bool DelTimer(TimerNodeBase &node) {
auto iter = timermap.find(node);
if (iter != timermap.end()) {
timermap.erase(iter);
return true;
}
return false;
}
//查询
bool CheckTimer() {
auto iter = timermap.begin();
if (iter != timermap.end() && iter->expire <= GetTick()) {
iter->func(*iter);
timermap.erase(iter);
return true;
}
return false;
}
//时间差
time_t TimeToSleep() {
auto iter = timermap.begin();
if (iter == timermap.end()) {
return -1;
}
time_t diss = iter->expire-GetTick();
return diss > 0 ? diss : 0;
}
private:
static int64_t GenID() {
return gid++;
}
static int64_t gid;
//std::less<T>是一个类的名称,是C++官方给你写好了的一个Comparator,里面的operator()也帮你重载好了的。
set<TimerNode, std::less<>> timermap;
};
int64_t Timer::gid = 0;
int main() {
int epfd = epoll_create(1);
unique_ptr<Timer> timer = make_unique<Timer>();
int i =0;
//[&]:以引用形式捕获所有外部变量,也就是外部变量均可用
timer->AddTimer(1000, [&](const TimerNode &node) {
cout << Timer::GetTick() << "node id:" << node.id << " revoked times:" << ++i << endl;
});
timer->AddTimer(1000, [&](const TimerNode &node) {
cout << Timer::GetTick() << "node id:" << node.id << " revoked times:" << ++i << endl;
});
timer->AddTimer(3000, [&](const TimerNode &node) {
cout << Timer::GetTick() << "node id:" << node.id << " revoked times:" << ++i << endl;
});
//auto:当编译器能够在一个变量的声明时候就推断出它的类型,那么你就能够用auto关键字来作为他们的类型
auto node = timer->AddTimer(2100, [&](const TimerNode &node) {
cout << Timer::GetTick() << "node id:" << node.id << " revoked times:" << ++i << endl;
});
timer->DelTimer(node);
cout << "now time:" << Timer::GetTick() << endl;
epoll_event ev[64] = {0};
while (true) {
/*
-1 永久阻塞
0 没有事件立刻返回,有事件就拷贝到 ev 数组当中
t > 0 阻塞等待 t ms,
timeout 最近触发的定时任务离当前的时间
*/
int n = epoll_wait(epfd, ev, 64, timer->TimeToSleep());
for (int i = 0; i < n; i++) {
/**/
}
/* 处理定时事件*/
while(timer->CheckTimer());
}
return 0;
}
2.3.4 运行结果
3. 扩展知识
3.1 std::function
std::function对C++中各种可调用实体(普通函数、Lambda表达式、函数指针、以及其它函数对象等)的封装,形成一个新的可调用的std::function对象,简化调用
# include <iostream>
# include <functional>
typedef std::function<int(int, int)> comfun;
// 普通函数
int add(int a, int b) { return a + b; }
// lambda表达式
auto mod = [](int a, int b){ return a % b; };
// 函数对象类
struct divide{
int operator()(int denominator, int divisor){
return denominator/divisor;
}
};
int main(){
comfun a = add;
comfun b = mod;
comfun c = divide();
std::cout << a(5, 3) << std::endl;
std::cout << b(5, 3) << std::endl;
std::cout << c(5, 3) << std::endl;
}
3.2 lambda表达式
lambda是匿名的,lambda表达式就是一段可调用的代码。主要适合于只用到一两次的简短代码段。
# include <iostream>
int fun3(int x, int y){
auto f = [](int x, int y) { return x + y; }; //创建lambda表达式,如果参数列表为空,可以省去()
std::cout << f(x, y) << std::endl; //调用lambda表达式
}
int main(){
fun3(300, 300);
}
3.3 结构体的构造函数
类似于成员函数,但是在初始化成员方面,构造函数显然更方便。
注意:
1. 构造函数冒号的左边和构造函数相同,名字与结构体名相同,还有一个形参列表
2. 如果形参名与成员变量名不同,则可以用成员变量名(形参名)(示例grade变量),多个成员用逗号分割。如果形参名与成员变量名相同,则用{this->成员变量名=形参名;}(示例name变量)来初始化,注意用“->”访问成员变量
3. 可以直接用变量名(值)(示例name变量)直接给变量赋值
4. {}一定要有,即使没有用到this来初始化变量,也要加上{}
5. 形参列表可以为空,然后用第3点的方法初始化变量
6. 若无构造函数,系统会赠送初始值
7. this是指向本身的指针,所以访问成员变量用->
3.4 using的用法
1. 命令空间的using声明
using std::cin; //必须每一个都有独立的using声明
using std::cout; using std::endl; //写在同一行也需要独立声明
2. 在子类中引用基类成员
3. 使用using起别名
typedef std::vector<int> intvec;
using intvec = std::vector<int>; //这两个写法是等价的
推荐一个不错的学习网站 C/C++后台高级服务器https://ke.qq.com/course/417774?flowToken=1010783