注:该文用于个人学习记录和知识交流,如有不足,欢迎指点。
一、定时器是什么?
组织管理大量延时任务的模块
二、定时器解决了什么问题?
不过度占用线程,高效处理定时任务
三、怎么实现的?
- 组织大量延时任务的数据结构(容器)
- 触发最近将超时任务的机制
1. 容器的选择
两类
- 对任务按触发时间进行排序:红黑树、最小堆
- 对执行顺序进行组织:时间轮,针对当前时间指针做偏移
2. 常用的触发机制
两种方式:
- io多路复用的最后一个超时参数
- 将定时器转化为io处理,timefd
3. 是不是只要有超时的接口,都可以用来实现定时器检测?
答:
- 理论上是的,比如使用sleep(),但是很少这样使用,因为定时器一般都是用在业务当中。
- 比如我们总需要一个事件触发,然后再插入定时任务吧,还有一个触发机制,执行定时任务,这时候使用sleep()局限性很多。
- 以服务器为例,我使用epoll_wait()的最后一个参数作为定时时间,可以兼备同时处理io事件跟定时任务,这是sleep不能实现的。
四、代码实现
这里采用红黑数+epoll_wait()最后一个时间参数
1. 接口:
- 添加一个延时任务
- 删除一个延时任务
- 检测触发延时任务
- 查看是否还有延时任务
2. 细节:
- 时间采用steady_clock:单调递增时钟,不会随系统时间变化而更改
- 在已知插入任务最晚执行,采用
emplace_hint:建议迭代器从某位置开始检查,减少查找位置的开销。
cpp
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;
}
};
3. 补充知识点:
| 方法 | 迭代类型 | 可修改性(容器非 const) | 指向位置 | 遍历方式(++ 方向) | 核心用途 |
|---|---|---|---|---|---|
| begin() | 正向普通迭代器 | 可读写(仅改值,键不可改) | 容器首元素 | 向尾元素移动 | 正向遍历 + 修改容器值 |
| cbegin() | 正向常量迭代器 | 只读(仅访问,键、值均不可改) | 容器首元素 | 向尾元素移动 | 正向遍历,确保不修改容器内容 |
| rbegin() | 逆向普通迭代器 | 可读写(仅改值,键不可改) | 容器尾元素 | 向首元素移动 | 逆向遍历 + 修改容器值 |
| crbegin() | 逆向常量迭代器 | 只读(仅访问,键、值均不可改) | 容器尾元素 | 向首元素移动 | 逆向遍历,确保不修改容器内容 |
| 函数 | 核心逻辑 |
|---|---|
emplace |
容器自行遍历红黑树(map/multimap 的底层结构)找插入位置,构造并插入元素,时间复杂度 O(logN) |
emplace_hint |
接收一个迭代器提示 (预期的插入位置),容器从该位置开始检查,减少查找位置的开销;若提示准确,插入时间复杂度接近 O(1) 注意:提示仅为 "建议"------ 若提示位置错误,容器仍会正常找正确位置(退化为 O(logN)),不会导致错误,只是失去优化效果。 |
4. 完整代码:
cpp
#include <iostream>
#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_); // 查找所有超时时间等于 node->timeout_ 的定时器节点,返回这些节点的迭代器范围(it.first 是起始,it.second 是结束)
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); // 删除已处理的定时器
}
}
bool IsEmpty() const {
return timer_map_.empty();
}
private:
std::multimap<uint64_t, TimerNode*> timer_map_;
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
int main() {
int epfd = epoll_create1(0);
if (epfd == -1) {
std::cerr << "epoll_create error: " << errno << std::endl;
return -1;
}
Timer* timer = Timer::GetInstance();
int i = 0;
timer->AddTimeout(1000, [&]() {
std::cout << "Timeout 1 second:" << i++ << std::endl;
});
timer->AddTimeout(2000, [&]() {
std::cout << "Timeout 2 seconds:" << i++ << std::endl;
});
auto node = timer->AddTimeout(3000, [&]() {
std::cout << "Timeout 3 seconds:" << i++ << std::endl;
});
timer->DelTimeout(node);
epoll_event evs[512];
while (true) {
int n = epoll_wait(epfd, evs, 512, timer->WaitTime());
if (n == -1) {
if (errno == EINTR) {
continue; // Interrupted by a signal, retry
}
std::cerr << "epoll_wait error: " << errno << std::endl;
break;
}
// 处理延时任务
timer->HandleTimeout();
if (timer->IsEmpty()) {
break; // 所有定时器任务已处理完,退出循环
}
}
return 0;
}
运行结果:
