1.定时器模块构成
1.1组织方式:
添加任务、有效地组织方式、数据结构
可以根据过期时间/执行顺序组织定时任务
根据执行序组织任务
根据执行序组织定时任务
实现一个时间移动的机制
addTimer(1000,func)//将任务挂到对应节点上
addTimer(2000,func)//
addTimer(3000,func)
触发方式:
时间指针不断移动,当移动到挂有任务的节点时,就把该节点的所有任务都处理掉
1.2 根据过期时间组织任务
触发方式:
根据数据结构找到最小值(即最近要触发的定时任务)
struct TimerNode{
time_t expire;//过期时间:now()+time
func_t func;//处理定时任务
void* ctx;//作为函数参数,内容为func依赖的上下文
};
{ int i=10;
Role * role;
addTimer(1000,func);}
异步行为:不占用线程方式实现定时器
不会阻塞,在到达过期时间时调用func,所以要保存上下文
1.3检测机制:希望快速找到最小值
红黑树
最小堆:描述父子之间的关系(父节点小于子节点)
组织结构:不需要完全有序,只需要相对有序
2.定时器模块驱动方式
按照触发时间有序组织:
now() 与 min(TimerNode expire) 比较
按照执行序进行组织
实现时间指针移动的机制
但不管是上面哪种方式都不可能一直占用时间片进行检测
驱动方式:间隔时间后再来比较
2.1跟网络模块协同处理
A. epoll_wait
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
//1) int epfd: epoll_create()函数返回的epoll实例的句柄。
//2) struct epoll_event * events: 接口的返回参数,epoll把发生的事件的集合从内核复制到 events数组中。events数组是一个用户分配好大小的数组,数组长度大于等于maxevents。
//3) int maxevents: 表示本次可以返回的最大事件数目,通常maxevents参数与预分配的events数组的大小是相等的。
//4) int timeout: 表示在没有检测到事件发生时最多等待的时间,超时时间(>=0),单位是毫秒ms,-1表示阻塞,0表示不阻塞。
这里通过timeout将网络模型和定时事件联系起来
下图为redis处理定时任务方式
//如果在一定时间内没有收到网络时间,就可以处理定时任务了
retval = epoll_wait(state->epfd,state->events,eventLoop->setsize,
tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1);//tvp是查看定时任务过期时间,如果大于0,则将检测时间置为当前定时任务最小的过期时间
B.希望把定时任务当作IO一样,按照事件处理
timefd
C.usleep 占用一个线程,检测所有的定时器的任务
2.2定时器C++实现
#include <time.h>
#include<sys/epoll.h>
#include<iostream>
#include<functional>
#include <set>
#include<memory>
#include<unistd.h>
using namespace std;
struct TimerBaseNode {
time_t expire;
int64_t id;
};
struct TimerNode : public TimerBaseNode {
using CallBack = std::function<void(const TimerNode & node)>;
CallBack func;
TimerNode(int64_t id, time_t expire, CallBack func) :func(std::move(func)) {
this->expire = expire;
this->id = id;
}
};
bool operator<(const TimerBaseNode &ld, const TimerBaseNode &rd) {
if (ld.expire < rd.expire) {//定时任务排序方式:如果过期时间相同,那么要按照插入id比较
return true;
}
else if (ld.expire > rd.expire) return false;
else return ld.id < rd.id;
}
class Timer {
public:
static inline time_t GetTick() {
uint64_t t;
struct timespec ti;
clock_gettime(CLOCK_MONOTONIC, &ti);
t = (uint64_t)ti.tv_sec * 1000;
t += ti.tv_nsec / 1000000;
return t;
}
TimerBaseNode AddTimer(time_t msec, TimerNode::CallBack func) {
time_t expire = GetTick() + msec;
auto pairs=timeouts.emplace(GetID(), expire, std::move(func));
return static_cast<TimerBaseNode>(*pairs.first);
}
bool DelTimer(TimerBaseNode & node) {
auto iter = timeouts.find(node);
if (iter != timeouts.end()) {
timeouts.erase(iter);
return true;
}
return false;
}
void HandleTimer(time_t now) {
auto iter = timeouts.begin();
while (iter != timeouts.end() && iter->expire <= now) {
iter->func(*iter);
iter = timeouts.erase(iter);
continue;
}
}
int TimeToSleep() {//计算定时事件最小过期时间和当前时间的差值
//如果在该时间段内没有网络事件到达就去处理定时任务
auto iter = timeouts.begin();
if (iter == timeouts.end()) {
return -1;
}
int diss = iter->expire - GetTick();
return diss > 0 ? diss : 0;
}
private:
static inline int64_t GetID() {
return gid++;
}
static int64_t gid;
set <TimerNode,std::less<>> timeouts;//set底层为红黑树
};
int64_t Timer::gid = 0;
int main() {
int epfd = epoll_create(1);
unique_ptr<Timer> timer = make_unique<Timer>();
epoll_event ev[256] = {};
int i = 0;
timer->AddTimer(1000, [&](const TimerNode & node) {
i++;
cout << "node id: " << node.id <<"hello yajie i is" << i<< endl;
});
while (true) {
int n = epoll_wait(epfd, ev, 256, timer->TimeToSleep());
time_t now = Timer::GetTick();
for (int i = 0; i < n; i++) {
//处理网络事件
}
//处理定时事件
}
close(epfd);
return 0;
}