Linux C++ 基于 timerfd + epoll 实现高性能定时器队列(完整源码 + 超详细解析)

文章简介

在 Linux 服务器开发、网络编程、后台服务中,定时任务是刚需:延迟执行、定点执行、周期性轮询任务随处可见。

传统方案如 alarm + signalsetitimer 存在信号干扰、不支持多定时器、精度低、无法和 IO 多路复用结合等缺点。

本文带你从零实现一套工业级定时器框架:

  • 底层基于 Linux 专属 timerfd 高精度定时器
  • 配合 epoll IO 多路复用 统一管理成千上万个定时器
  • 封装三层架构:Timer 单个定时器 → TimerQueue 定时器队列 → ScheduledThreadPool 定时线程池
  • 支持:定点执行、延迟执行、周期性循环执行、随时取消任务
  • 全部源码可直接编译运行,附带核心 API 知识点详解、架构流程拆解

运行环境:Linux 内核 2.6.25 及以上、C++11 及以上


一、核心知识点前置讲解

1. timerfd 是什么?

timerfd 是 Linux 内核提供的定时器文件描述符,把定时器变成一个普通文件 fd:

  • 定时器到期时,fd 会变成可读
  • 可以直接被 epoll / select / poll 监听
  • 支持纳秒级高精度、支持单次 / 周期性定时
  • 无信号、无阻塞、线程安全,是服务器开发首选定时器方案
核心 API 详解
① timerfd_create 创建定时器
cs 复制代码
#include <sys/timerfd.h>
int timerfd_create(int clockid, int flags);
  • clockid 时钟类型
    • CLOCK_MONOTONIC:单调时钟(推荐),开机后一直递增,不受系统时间修改、网络校时影响
    • CLOCK_REALTIME:系统实时时间,修改系统时间会打乱定时
  • flags :填 0 即可,也可组合 TFD_NONBLOCK 非阻塞、TFD_CLOEXEC 子进程自动关闭
  • 返回值:成功返回定时器 fd,失败返回 -1
② timerfd_settime 设置 / 启动定时器
cs 复制代码
int timerfd_settime(int fd, int flags, const struct itimerspec *new_val, struct itimerspec *old_val);

作用:配置首次触发时间、循环间隔,启动定时器。

③ 关键结构体 struct itimerspec
cs 复制代码
struct itimerspec {
    struct timespec it_interval; // 循环周期
    struct timespec it_value;    // 首次触发延迟时间
};
struct timespec {
    time_t tv_sec;  // 秒
    long   tv_nsec; // 纳秒 0~999999999
};
  • it_value:多久后第一次触发,全 0 则定时器不启动
  • it_interval:第一次触发后,每隔多久循环一次,全 0 则只触发一次
  • 时间换算:1ms = 1000us = 1000000ns
④ read 读取定时器事件

timerfd 到期后,必须调用 read 读取一个 uint64_t 数值(到期次数),不读取会一直触发 epoll 可读事件


2. epoll 是什么?

epoll 是 Linux 高性能 IO 多路复用,用来同时监听大量文件描述符(socket、timerfd、pipe 等)。

核心三大 API
① epoll_create1 创建 epoll 实例
cs 复制代码
#include <sys/epoll.h>
int epoll_create1(int flags);
  • 推荐传参:EPOLL_CLOEXEC,进程 exec 时自动关闭 fd,防止句柄泄漏
  • 比老旧 epoll_create 更安全、现代项目标准写法
② epoll_ctl 增删改监听 fd
cs 复制代码
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *ev);
  • EPOLL_CTL_ADD:添加 fd 到 epoll 监听
  • EPOLL_CTL_DEL:从 epoll 移除 fd
  • 监听事件填 EPOLLIN:监听可读事件(timerfd 到期就是可读)
③ epoll_wait 阻塞等待事件
cs 复制代码
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
  • 阻塞等待监听的 fd 产生事件
  • 返回就绪事件个数,遍历处理每个定时器的到期回调

二、整体架构设计

整套代码分为三层,自上而下解耦:

  1. ScheduledThreadPool:对外简易接口层,给业务直接调用

    • AddRunAt 指定时间执行
    • AddRunAfter 延迟多久执行
    • AddRunEvery 周期性循环执行
    • Cancel 取消定时任务
  2. TimerQueue:定时器队列管理层

    • 内部创建 epoll、独立工作线程
    • 管理所有 Timer 对象,fd 与定时器映射
    • 循环监听 epoll 事件,分发到期任务
  3. Timer:单个定时器封装层

    • 封装 timerfd 创建、参数设置、事件读取、回调执行
    • 支持单次 / 重复定时、重置、关闭资源

工作流程:业务调用线程池接口 → 创建 Timer → 加入 TimerQueue 的 epoll 监听 → 定时器到期 epoll 触发 → 执行任务回调。


三、完整源码 + 逐行注释

1. 时间戳工具类 Timestamp.hpp(依赖基础)

cs 复制代码
#ifndef TIMESTAMP_HPP
#define TIMESTAMP_HPP
#include <stdint.h>
#include <chrono>

namespace tulun
{
    class Timestamp
    {
    public:
        // 1秒 = 1000000 微秒
        static const int KMinPerSec = 1000000;
    private:
        int64_t m_microSec; // 存储微秒时间戳
    public:
        Timestamp() : m_microSec(0) {}
        Timestamp(int64_t micro) : m_microSec(micro) {}

        // 获取当前时间戳
        static Timestamp Now()
        {
            auto now = std::chrono::system_clock::now();
            auto duration = std::chrono::duration_cast<std::chrono::microseconds>(now.time_since_epoch());
            return Timestamp(duration.count());
        }

        int64_t getMicro() const { return m_microSec; }

        // 无效时间
        static Timestamp Invalid() { return Timestamp(0); }
    };

    // 时间戳增加毫秒
    inline Timestamp addTimeMilloc(Timestamp ts, size_t ms)
    {
        return Timestamp(ts.getMicro() + ms * 1000);
    }
}
#endif

2. 定时器类头文件 Timer.hpp

cs 复制代码
#include <functional>
#include <utility>
#include "Timestamp.hpp"
#ifndef TIMER_HPP
#define TIMER_HPP

namespace tulun
{
    // 定时器回调函数类型:无参无返回值
    using TimerCallback = std::function<void(void)>;

    // 单个定时器类
    class Timer
    {
    private:
        int m_timerfd;                // timerfd 文件描述符
        TimerCallback m_callback;     // 到期回调函数
        tulun::Timestamp m_expiration;// 到期时间戳
        size_t m_interval;            // 循环间隔 毫秒
        bool m_repeat;                // 是否循环定时
        bool settimer();              // 设置timerfd定时参数
    public:
        Timer();
        ~Timer();
        // 初始化定时器:回调、到期时间、间隔
        bool init(const TimerCallback &cb, const Timestamp &when, size_t interval);
        bool resetTimer(const Timestamp &newtime); // 重置定时
        void handleEvent();                        // 处理到期事件
        int getTimerFd() const;                    // 获取fd
        bool closeTimer();                         // 关闭释放资源
    };
    // 定时器ID:fd + 定时器指针,用于取消任务
    using TimerId = std::pair<int, Timer *>;
}
#endif

3. 定时器实现 Timer.cpp

cs 复制代码
#include <sys/timerfd.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include "Logger.hpp"   // 自行实现日志即可,可用printf替代
#include "Timer.hpp"

namespace tulun
{
    // 计算当前到目标时间的差值,转为timerfd需要的秒+纳秒
    static struct timespec howMuchTimeFormNow(const Timestamp &when)
    {
        int64_t microseconds = when.getMicro() - Timestamp::Now().getMicro();
        if (microseconds < 100) // 最小100微秒,防止立即触发
            microseconds = 100;

        struct timespec ts;
        ts.tv_sec = microseconds / Timestamp::KMinPerSec;        // 转秒
        ts.tv_nsec = (microseconds % Timestamp::KMinPerSec)*1000;// 剩余微秒转纳秒
        return ts;
    }

    // 设置并启动定时器
    bool Timer::settimer()
    {
        bool ret = true;
        struct itimerspec new_value = {};
        // 设置循环间隔
        new_value.it_interval.tv_sec  = m_interval / 1000;
        new_value.it_interval.tv_nsec = (m_interval % 1000) * 1000 * 1000;
        // 设置首次触发时间
        new_value.it_value = howMuchTimeFormNow(m_expiration);

        // 系统调用设置定时器
        if(::timerfd_settime(m_timerfd, 0, &new_value, nullptr) < 0)
        {
            LOG_ERROR << "timerfd_settime fail : " << strerror(errno);
            ret = false;
        }
        return ret;
    }

    // 构造函数初始化成员
    Timer::Timer()
        : m_timerfd(-1),m_callback(nullptr),m_expiration(),m_interval(0),m_repeat(false)
    {}

    // 析构自动关闭定时器
    Timer::~Timer()
    {
        closeTimer();
    }

    // 初始化timerfd + 配置参数
    bool Timer::init(const TimerCallback &cb, const Timestamp &when, size_t interval)
    {
        bool ret = true;
        // 创建单调时钟timerfd
        m_timerfd = ::timerfd_create(CLOCK_MONOTONIC, 0);
        if (m_timerfd < 0)
        {
            LOG_FATAL << "timerfd_create fail : " << strerror(errno);
            return false;
        }
        m_callback = cb;
        m_expiration = when;
        m_interval = interval;
        m_repeat = (interval > 0); // 间隔大于0就是循环定时器
        settimer();
        return ret;
    }

    // 重置循环定时器时间
    bool Timer::resetTimer(const Timestamp &newtime)
    {
        if (m_repeat)
        {
            m_expiration = tulun::addTimeMilloc(newtime, m_interval);
            settimer();
            return true;
        }
        m_expiration = Timestamp::Invalid();
        return false;
    }

    // epoll触发后执行:读取事件 + 调用业务回调
    void Timer::handleEvent()
    {
        uint64_t expire_cnt = 0;
        // 必须read清空事件,否则epoll一直触发
        if (::read(m_timerfd, &expire_cnt, sizeof(expire_cnt)) != sizeof(expire_cnt))
        {
            LOG_ERROR << "read timerfd fail ";
            return;
        }
        // 执行定时任务
        if(m_callback) m_callback();
    }

    int Timer::getTimerFd() const { return m_timerfd; }

    // 关闭fd、释放资源
    bool Timer::closeTimer()
    {
        if (m_timerfd > 0)
        {
            close(m_timerfd);
            m_timerfd = -1;
            m_callback = nullptr;
            m_repeat = false;
            return true;
        }
        return false;
    }
}

4. 定时器队列头文件 TimerQueue.hpp

cs 复制代码
#include <sys/epoll.h>
#include <unordered_map>
#include <vector>
#include <thread>
#include <atomic>
#include <mutex>
#include "Logger.hpp"
#include "Timestamp.hpp"
#include "Timer.hpp"

#ifndef TIMERQUEUE_HPP
#define TIMERQUEUE_HPP
namespace tulun
{
    // 定时器队列:统一管理所有定时器 + epoll监听
    class TimerQueue
    {
    private:
        static const int eventsize = 16; // epoll事件数组初始大小
        int m_epollfd;                    // epoll实例fd
        int m_timeout;                    // epoll_wait超时
        std::vector<struct epoll_event> m_events; // 存储就绪事件
        std::unordered_map<int, Timer *> m_timers; // fd -> Timer映射
        std::atomic_bool m_stop;          // 线程安全停止标记
        std::once_flag m_flag;            // 保证stop只执行一次
        std::thread m_worderThread;       // 事件监听工作线程

        void loop();    // 线程循环:epoll_wait处理事件
        void init();    // 初始化epoll和线程
        void stopQueue();// 停止并释放所有资源
    public:
        TimerQueue(int timeout = -1);
        ~TimerQueue();
        TimerId addTimer(const TimerCallback &cb, const Timestamp &when, size_t interval);
        void cancel(TimerId timerid);
        void stop();
    };
}
#endif

5. 定时器队列实现 TimerQueue.cpp

cs 复制代码
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include "Timer.hpp"
#include "TimerQueue.hpp"

namespace tulun
{
    // 工作线程主循环:阻塞监听epoll事件
    void TimerQueue::loop()
    {
        while (!m_stop)
        {
            // 等待epoll事件触发
            int n = ::epoll_wait(m_epollfd, m_events.data(), m_events.size(), m_timeout);
            // 遍历所有就绪定时器
            for (int i = 0; i < n; ++i)
            {
                int fd = m_events[i].data.fd;
                auto it = m_timers.find(fd);
                if (it != m_timers.end())
                {
                    it->second->handleEvent(); // 执行定时回调
                }
            }
            // 事件过多则扩容数组
            if (n >= m_events.size())
                m_events.resize(m_events.size() * 2);
        }
    }

    // 初始化epoll + 启动工作线程
    void TimerQueue::init()
    {
        // 创建安全的epoll实例
        m_epollfd = ::epoll_create1(EPOLL_CLOEXEC);
        if (m_epollfd < 0)
        {
            LOG_FATAL << "epoll_create1 fail: " << strerror(errno);
            return;
        }
        try
        {
            m_stop = false;
            m_worderThread = std::thread(&TimerQueue::loop, this);
        }
        catch (...)
        {
            close(m_epollfd);
            m_epollfd = -1;
            m_stop = true;
        }
    }

    // 停止队列、释放所有定时器和线程
    void TimerQueue::stopQueue()
    {
        m_stop = true;
        if (m_worderThread.joinable())
            m_worderThread.join();

        // 逐个关闭并销毁定时器
        for (auto &pair : m_timers)
        {
            pair.second->closeTimer();
            delete pair.second;
        }
        m_timers.clear();
        close(m_epollfd);
        m_epollfd = -1;
    }

    TimerQueue::TimerQueue(int timeout)
        : m_epollfd(-1), m_timeout(timeout), m_stop(true)
    {
        m_events.resize(eventsize);
        init();
    }

    TimerQueue::~TimerQueue()
    {
        stop();
    }

    // 添加定时器:创建Timer、加入epoll、存入映射表
    TimerId TimerQueue::addTimer(const TimerCallback &cb, const Timestamp &when, size_t interval)
    {
        TimerId ret{-1, nullptr};
        Timer *ptimer = new Timer();
        if (!ptimer->init(cb, when, interval))
            return ret;

        // 注册timerfd到epoll,监听可读事件
        struct epoll_event evt;
        evt.events = EPOLLIN;
        evt.data.fd = ptimer->getTimerFd();
        if (::epoll_ctl(m_epollfd, EPOLL_CTL_ADD, ptimer->getTimerFd(), &evt) < 0)
        {
            LOG_ERROR << "epoll_ctl add fail " << strerror(errno);
            return ret;
        }
        m_timers[ptimer->getTimerFd()] = ptimer;
        ret.first = ptimer->getTimerFd();
        ret.second = ptimer;
        return ret;
    }

    // 取消指定定时器
    void TimerQueue::cancel(TimerId timerid)
    {
        auto it = m_timers.find(timerid.first);
        if (it != m_timers.end())
        {
            m_timers.erase(it);
            it->second->closeTimer();
            delete it->second;
        }
    }

    // 保证只停止一次
    void TimerQueue::stop()
    {
        std::call_once(m_flag, &TimerQueue::stopQueue, this);
    }
}

6. 定时线程池对外封装 ScheduledThreadPool.hpp

cs 复制代码
#include "Timestamp.hpp"
#include "Timer.hpp"
#include "TimerQueue.hpp"
#include <functional>
#include <chrono>

#ifndef SCHEDULEDTHREADPOOL_HPP
#define SCHEDULEDTHREADPOOL_HPP

namespace tulun
{
    // 业务直接使用的定时线程池,封装底层细节
    class ScheduledThreadPool
    {
    private:
        tulun::TimerQueue m_queue;
    public:
        // 指定时间执行一次
        TimerId AddRunAt(const Timestamp &time, const TimerCallback &cb)
        {
            return m_queue.addTimer(cb, time, 0);
        }
        // 延迟delay毫秒后执行一次
        TimerId AddRunAfter(size_t delay, const TimerCallback &cb)
        {
            Timestamp time(addTimeMilloc(Timestamp::Now(), delay));
            return AddRunAt(time, cb);
        }
        // 每隔interval毫秒循环执行
        TimerId AddRunEvery(size_t interval, const TimerCallback &cb)
        {
            Timestamp time(addTimeMilloc(Timestamp::Now(), interval));
            return m_queue.addTimer(cb, time, interval);
        }
        // 取消定时任务
        void Cancel(TimerId timerid)
        {
            m_queue.cancel(timerid);
        }
    };
}
#endif

7. 测试代码 Test05_15Timer.cpp

cs 复制代码
#include "ScheduledThreadPool.hpp"
#include <iostream>
#include <chrono>
using namespace std;

// 定时任务回调函数
void taskFunc()
{
    cout << "定时任务执行:当前时间触发" << endl;
}

int main()
{
    tulun::ScheduledThreadPool spool;

    // 每5秒执行一次周期性任务
    spool.AddRunEvery(5000, taskFunc);

    // 主线程休眠60秒,让定时任务持续运行
    std::this_thread::sleep_for(std::chrono::seconds(60));
    return 0;
}

四、编译与运行

编译命令

复制代码
g++ Test05_15Timer.cpp Timer.cpp TimerQueue.cpp -o timer -pthread -std=c++11

运行

复制代码
./timer

效果:每 5 秒打印一次任务日志,持续 60 秒后程序退出。


五、架构优势总结

  1. 高性能:epoll 海量管理定时器,事件驱动,无轮询空转耗 CPU
  2. 高精度:基于 timerfd 纳秒级定时,远优于传统信号定时
  3. 解耦分层:底层细节屏蔽,业务只需调用简单接口
  4. 功能齐全:支持定点、延迟、周期、取消任务
  5. 服务器适配:可无缝嵌入 Reactor 反应堆模型、网络框架

六、适用场景

  • Linux 后台服务定时心跳、定时上报
  • 网络服务器超时管理(连接超时、读写超时)
  • 定时清理任务、定时日志切割
  • 游戏服务器帧定时、周期性逻辑调度
相关推荐
Lochor Lee1 小时前
C++学习笔记——输入输出的格式
c++·笔记·学习
皓月盈江1 小时前
Linux Ubuntu系统使用Docker搭建vulhub靶场环境
linux·ubuntu·docker·tomcat·vulhub·漏洞靶场
WL_Aurora1 小时前
Java技术体系:JDK、JRE、JVM的关系与演进(2026最新版)
java·开发语言·jvm
念恒123061 小时前
Docker基础--namespace空间隔离实战(包含部分指令)
linux·运维·服务器
j7~1 小时前
【Linux】基础IO超万字解析(文件描述符)(2)
linux·运维·服务器·c++·file·重定向·文件描述
WangLanguager1 小时前
Linux命令adduser详细介绍
linux·运维·服务器
吃好睡好便好1 小时前
在Matlab中绘制二维等高线图
开发语言·人工智能·学习·算法·matlab
lingzhilab1 小时前
零知派ESP32——TCS3200高精度RGB颜色识别系统教程
c++·mfc
wkj0011 小时前
JavaScript模块化技术进程详解
开发语言·javascript·ecmascript