定时器设计--红黑树

目录

1. 定时器触发方式

1.1 网络事件和定时事件在一个线程中处理

1.2 网络事件和定时事件在不同线程中处理;

2. 定时器设计

2.1 接口设计

2.2 数据结构设计

2.3 整体思路

2.3.1 获取当前时间的接口

2.3.2 相同触发时间的定时任务处理

2.3.3 完整代码

2.3.4 运行结果

3. 扩展知识

3.1 std::function

3.2 lambda表达式

3.3 结构体的构造函数

3.4 using的用法


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  

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值