Linux C/C++ 学习日记(57):定时器

注:该文用于个人学习记录和知识交流,如有不足,欢迎指点。

一、定时器是什么?

组织管理大量延时任务的模块

二、定时器解决了什么问题?

不过度占用线程,高效处理定时任务

三、怎么实现的?

  • 组织大量延时任务的数据结构(容器)
  • 触发最近将超时任务的机制

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;
}

运行结果:

相关推荐
西岸行者5 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意5 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码5 天前
嵌入式学习路线
学习
毛小茛5 天前
计算机系统概论——校验码
学习
babe小鑫5 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms5 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下5 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。5 天前
2026.2.25监控学习
学习
im_AMBER5 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J5 天前
从“Hello World“ 开始 C++
c语言·c++·学习