文章简介
在 Linux 服务器开发、网络编程、后台服务中,定时任务是刚需:延迟执行、定点执行、周期性轮询任务随处可见。
传统方案如 alarm + signal、setitimer 存在信号干扰、不支持多定时器、精度低、无法和 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 产生事件
- 返回就绪事件个数,遍历处理每个定时器的到期回调
二、整体架构设计
整套代码分为三层,自上而下解耦:
-
ScheduledThreadPool:对外简易接口层,给业务直接调用
AddRunAt指定时间执行AddRunAfter延迟多久执行AddRunEvery周期性循环执行Cancel取消定时任务
-
TimerQueue:定时器队列管理层
- 内部创建 epoll、独立工作线程
- 管理所有 Timer 对象,fd 与定时器映射
- 循环监听 epoll 事件,分发到期任务
-
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 秒后程序退出。
五、架构优势总结
- 高性能:epoll 海量管理定时器,事件驱动,无轮询空转耗 CPU
- 高精度:基于 timerfd 纳秒级定时,远优于传统信号定时
- 解耦分层:底层细节屏蔽,业务只需调用简单接口
- 功能齐全:支持定点、延迟、周期、取消任务
- 服务器适配:可无缝嵌入 Reactor 反应堆模型、网络框架
六、适用场景
- Linux 后台服务定时心跳、定时上报
- 网络服务器超时管理(连接超时、读写超时)
- 定时清理任务、定时日志切割
- 游戏服务器帧定时、周期性逻辑调度