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

运行结果:

相关推荐
楠了个难3 小时前
安服优-B-1 人体红外测温传感器——ZYNQ学习笔记23
笔记·学习
式5163 小时前
大模型学习基础(六) 强化学习(Reinforcement Learning,RL)初步1.4
学习
小猪佩奇TONY3 小时前
Linux 内核学习(13) --- linux 内核并发与竞态
linux·服务器·学习
黑客思维者3 小时前
机器学习011:监督学习【回归算法】(多项式回归)-- 从“猜咖啡温度”到预测万物
人工智能·学习·机器学习·回归·线性回归·监督学习·多项式回归
deng-c-f3 小时前
Linux C/C++ 学习日记(56):用户态网络缓存区
学习·缓存
d111111111d3 小时前
STM32平衡车开发计划2-电机编码器测速
笔记·stm32·单片机·嵌入式硬件·学习
黑客思维者3 小时前
机器学习010:监督学习【回归算法】(Lasso回归)-- 用“魔法剪刀”找到真正重要的信息
人工智能·学习·机器学习·回归·监督学习·回归算法·lasso
zhangrelay4 小时前
新旧交替-传统模式被逐步抛弃……(节选)
学习
deng-c-f4 小时前
Linux C/C++ 学习日记(55):原子操作(四):实现无锁队列
学习