延时任务定时器的实现
定时器是什么?
组织和管理大量延时任务的模块
定时器解决了什么问题?
在不过度占用线程的情况下,高效处理定时任务。
定时器如何解决问题?
1.通过数据结构组织大量延时任务
2.对将要超时的延时任务进行触发。
定时器的数据结构选择
红黑树:红黑树是一个有序二叉树,且插入和删除操作的复杂度小于AVL树,可以将任务按触发顺序进行排序,每次选择超时任务只需选择最左节点即可。
最小堆:最小堆中根节点永远是最小的,同样可以将任务按触发顺序加入到最小堆中,每次选择根节点即可
时间轮:将超时任务按顺序放入时间轮中,针对当前时间轮中的时间指针做偏移。
定时器的触发机制
利用 IO 多路复用最后一个参数(超时参数)
使用IO 多路复用系统调用如select/poll/epoll最后一个参数,将定时器的触发时机与网络事件的触发时机相同,从而将定时器何时触发交给IO多路复用
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();
}
// 网络事件和定时事件在不同线程中处理
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);
timerfd
利用 timerfd,将定时检测作为 IO 多路复用当中的事件进行处理;
#include <sys/timerfd.h>
// 创建一个定时器文件描述符,可以像网络 IO 一样,将这个 fd
交由 IO 多路复用来管理
int timerfd_create(int clockid, int flags);
// 设置一个触发时间,IO 多路复用将会检测这个过期事件,然后
通知应用程序定时事件就绪
int timerfd_settime(int fd, int flags,const struct itimerspec
*new_value,struct itimerspec
*old_value);
定时器实现
#pragma once// #include <sys/epoll.h>
// #include <unistd.h>
#include <map>
#include <functional>
#include <chrono>
//定时任务节点
class TimerNode {
public:friend class Timer;TimerNode(uint64_t timeout, std::function<void()> callback): timeout_(timeout), callback_(std::move(callback)) {}
private:int id;uint64_t timeout_;std::function<void()> callback_;
};class Timer {
public:static Timer* GetInstance() {static Timer instance;return &instance;}static uint64_t GetCurrentTime() {using namespace std::chrono;return duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count();}//添加定时任务TimerNode* AddTimeout(uint64_t diff, std::function<void()> cb) {auto node = new TimerNode(GetCurrentTime() + diff, std::move(cb));//如果节点超时时间小于红黑树的最大节点的超时时间,则使用插入if (timer_map_.empty() || node->timeout_ < timer_map_.rbegin()->first) {auto it = timer_map_.insert(std::make_pair(node->timeout_, std::move(node)));return it->second;//否则直接插入到红黑树的最右端} else {auto it = timer_map_.emplace_hint(timer_map_.crbegin().base(), std::make_pair(node->timeout_, std::move(node)));return it->second;}};//删除节点void DelTimeout(TimerNode* node) {auto it = timer_map_.equal_range(node->timeout_);for (auto iter = it.first; iter != it.second; ++iter) {if (iter->second == node) {timer_map_.erase(iter);break;}}}// 获取延时任务还需等待的时间int WaitTime() {auto iter = timer_map_.begin();if (iter == timer_map_.end()) {return -1;}uint64_t diff = iter->first - GetCurrentTime();return diff > 0 ? diff : 0;}//处理延时任务void HandleTimeout() {auto iter = timer_map_.begin();while (iter != timer_map_.end() && iter->first <= GetCurrentTime()) {iter->second->callback_();iter = timer_map_.erase(iter); // 删除已处理的定时器}}
private:std::multimap<uint64_t, TimerNode*> timer_map_;//使用mutimap存储每个定时任务,同一时间可能有多个任务Timer() = default;Timer(const Timer&) = delete;Timer& operator=(const Timer&) = delete;Timer(Timer&&) = delete;Timer& operator=(Timer&&) = delete;~Timer() {for (auto& pair : timer_map_) {delete pair.second;}}
};#define TimerInstance Timer::GetInstance
https://github.com/0voice