基于多线程的定时器
代码
#pragma once
#include <thread>
#include <atomic>
#include <functional>
#include <chrono>
#include <mutex>
class Timer {
public:
Timer();
Timer(int repeat);
~Timer();
// 启动定时器
template<typename F, typename... Args>
void start(int millisecond, F&& func, Args&&... args);
// 停止定时器
void stop();
private:
std::thread m_thread;
std::atomic<bool> m_active;
std::function<void()> m_func;
int m_period; // milliseconds
int m_repeat; // 触发次数,-1表示无限循环执行
std::mutex m_mutex; // 保护 m_repeat
};
// 构造函数和析构函数的实现
Timer::Timer() : m_active(false), m_period(0), m_repeat(-1) {}
Timer::Timer(int repeat) : m_active(false), m_period(0), m_repeat(repeat) {}
Timer::~Timer() {
stop();
if (m_thread.joinable()) {
m_thread.join();
}
}
// 停止定时器
void Timer::stop() {
m_active.store(false);
if (m_thread.joinable()) {
m_thread.join();
}
}
// 启动定时器
template<typename F, typename... Args>
void Timer::start(int millisecond, F&& func, Args&&... args) {
if (m_active.load()) {
return; // 定时器已在运行
}
m_period = millisecond;
m_func = std::bind(std::forward<F>(func), std::forward<Args>(args)...);
m_active.store(true);
m_thread = std::thread([this]() {
std::unique_lock<std::mutex> lock(m_mutex);
if (m_repeat < 0) {
while (m_active.load()) {
lock.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(m_period));
lock.lock();
if (!m_active.load()) {
return;
}
m_func();
}
} else {
while (m_repeat > 0) {
lock.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(m_period));
lock.lock();
if (!m_active.load()) {
return;
}
m_func();
m_repeat--;
}
}
});
m_thread.detach();
}
这个 C++ Timer 类实现了一个基于线程的定时器功能,支持周期性执行任务,并可设置执行次数(有限次或无限循环)。下面从功能实现、线程安全、潜在问题等方面进行分析:
1. 核心功能与设计思路
- 核心目标:通过独立线程实现定时任务调度,支持指定周期(毫秒)、执行次数(-1 表示无限循环)。
- 核心组件:
std::thread m_thread
:执行定时任务的线程。std::atomic<bool> m_active
:原子变量,标记定时器是否活跃(线程安全的状态控制)。std::function<void()> m_func
:存储待执行的任务(通过std::bind
绑定函数和参数)。m_period
:任务执行周期(毫秒)。m_repeat
:任务执行次数(-1 为无限循环),由std::mutex
保护(多线程访问安全)。
2. 关键方法分析
(1) 构造与析构
- 构造函数初始化状态(
m_active=false
),默认m_repeat=-1
(无限循环),也可通过参数指定执行次数。 - 析构函数调用
stop()
确保定时器停止,并join()
线程(避免线程资源泄漏)。
(2) start () 方法
- 模板函数,支持任意参数的函数作为任务(通过
std::forward
和std::bind
实现参数绑定)。 - 逻辑:
- 检查定时器是否已运行(
m_active
),避免重复启动。 - 初始化周期、任务函数,标记
m_active=true
。 - 启动新线程,根据
m_repeat
执行任务:- 无限循环(
m_repeat < 0
):持续睡眠m_period
毫秒后执行任务,直到m_active
为false
。 - 有限次数(
m_repeat > 0
):执行m_repeat
次后自动停止。
- 无限循环(
- 线程最终被
detach()
(与主线程分离,独立运行)。
- 检查定时器是否已运行(
(3) stop () 方法
- 标记
m_active=false
(通知线程退出),并join()
线程(等待线程结束,释放资源)。
3. 线程安全设计
- 状态控制:
m_active
为原子变量,确保多线程环境下的读写操作无数据竞争。 - 共享变量保护:
m_repeat
通过std::mutex
加锁访问(线程内部修改和判断时加锁),避免读写冲突。 - 任务函数赋值:
start()
中仅当m_active=false
时才更新m_func
,避免线程执行时任务函数被修改。
4. 潜在问题与局限性
(1) 线程管理风险
start()
中对线程执行detach()
,导致主线程无法可靠控制线程生命周期。若stop()
调用时线程已detach
,joinable()
返回false
,join()
不会执行,但线程可能仍在后台运行(直到自然结束),存在资源释放延迟风险。- 多次调用
start()
可能导致线程资源泄漏:若前一次detach
的线程尚未结束,新线程又被创建,旧线程可能仍在运行。
(2) 定时精度问题
- 实际执行周期 = 睡眠时间(
m_period
) + 任务函数执行时间。若任务函数执行耗时较长,会导致后续执行延迟,无法保证严格的周期性。
(3) 重复次数设计缺陷
m_repeat
在start()
中不会重置,若同一Timer
对象多次调用start()
,会继承上一次剩余的执行次数(例如:第一次m_repeat=3
执行 2 次后停止,第二次start()
会仅执行 1 次),不符合用户预期(通常希望每次start()
都从初始次数开始)。- 无法在
start()
时动态指定执行次数,只能通过构造函数设置,灵活性不足。
(4) 异常处理缺失
- 若
m_func
(任务函数)执行时抛出异常,会直接导致定时器线程终止,且m_active
仍为true
,定时器处于异常状态(无法继续执行,也无法通过stop()
正常停止)。
(5) 停止逻辑的延迟
stop()
设置m_active=false
后,需等待线程从sleep_for
中唤醒(最长延迟m_period
毫秒)才能退出,无法立即终止线程。
5. 改进建议
- 线程管理:使用
join()
替代detach()
,确保线程可被主线程主动等待结束(需调整start()
和stop()
的逻辑,避免重复join
)。 - 定时精度:采用 “绝对时间” 计算下次执行时刻(如
std::chrono::steady_clock
),补偿任务执行耗时,保证周期稳定。 - 重复次数重置:在
start()
中重置m_repeat
为构造函数指定的值(或允许start()
参数指定),避免状态残留。 - 异常处理:在任务函数执行处添加
try-catch
,捕获异常并处理(如终止定时器或记录日志)。 - 立即停止:使用条件变量(
std::condition_variable
)替代sleep_for
,支持stop()
时立即唤醒线程,减少停止延迟。
总结
该 Timer 类实现了基本的定时任务调度功能,线程安全设计较为基础,但在线程管理、定时精度、功能灵活性等方面存在明显缺陷,适合简单场景使用。若用于生产环境,需针对上述问题进行优化。
基于时间序列触发的定时器
代码
Timer.h
#pragma once
#include <functional>
#include <thread>
#include <chrono>
#include <map>
#include <algorithm>
// 前向声明
class TimerManager;
class Timer {
friend class TimerManager; // 允许TimerManager访问私有成员
public:
// 构造函数:默认无限循环
Timer() : m_time(now()), m_period(0), m_repeat(-1) {}
// 构造函数:指定重复次数(-1表示无限)
Timer(int repeat) : m_time(now()), m_period(0), m_repeat(repeat) {}
// 析构函数(无需特殊操作)
~Timer() = default;
// 绑定回调函数和参数,设置周期(毫秒)
template<typename F, typename ... Args>
void callback(int millisecond, F&& func, Args&&... args) {
m_period = millisecond;
// 绑定函数和参数(完美转发保持参数类型)
m_func = std::bind(std::forward<F>(func), std::forward<Args>(args)...);
// 首次触发时间 = 当前时间 + 周期(避免立即执行)
m_time = now() + millisecond;
}
// 执行回调函数,并更新下一次触发时间
void on_timer() {
if (!m_func || m_repeat == 0) {
return; // 无回调函数或已结束,直接返回
}
m_func(); // 执行回调
m_time += m_period; // 更新下一次触发时间
if (m_repeat > 0) {
m_repeat--; // 有限次数:减少剩余次数
}
}
private:
// 获取当前时间(毫秒级,从 epoch 开始)
static int64_t now() {
auto now = std::chrono::system_clock::now();
auto now_ms = std::chrono::time_point_cast<std::chrono::milliseconds>(now);
return now_ms.time_since_epoch().count();
}
private:
int64_t m_time; // 下一次触发时间点(毫秒)
std::function<void()> m_func; // 回调函数
int m_period; // 周期(毫秒)
int m_repeat; // 剩余重复次数(-1表示无限)
};
class TimerManager {
public:
// 调度无限循环的定时任务
template<typename F, typename ... Args>
void schedule(int millisecond, F&& func, Args&&... args) {
schedule(millisecond, -1, std::forward<F>(func), std::forward<Args>(args)...);
}
// 调度有限次数的定时任务
template<typename F, typename ... Args>
void schedule(int millisecond, int repeat, F&& func, Args&&... args) {
if (millisecond <= 0 || (repeat <= 0 && repeat != -1)) {
return; // 无效参数:周期必须>0,重复次数只能是-1(无限)或>0
}
Timer timer(repeat);
timer.callback(millisecond, std::forward<F>(func), std::forward<Args>(args)...);
// 按触发时间插入到有序集合(multimap自动按key排序)
m_timers.insert({timer.m_time, timer});
}
// 检查并执行所有到期的定时任务
void update() {
if (m_timers.empty()) {
return;
}
int64_t current_time = Timer::now();
auto it = m_timers.begin();
// 遍历所有已到期的定时器(multimap按时间排序,前面的先到期)
while (it != m_timers.end()) {
if (it->first > current_time) {
break; // 当前定时器未到期,后续的也不会到期(有序性保证)
}
// 保存当前定时器副本(避免erase后原对象被销毁)
Timer current_timer = it->second;
// 移除已到期的定时器
it = m_timers.erase(it);
// 执行回调并更新下一次触发时间
current_timer.on_timer();
// 若仍需重复执行,重新插入到集合
if (current_timer.m_repeat != 0) {
m_timers.insert({current_timer.m_time, current_timer});
}
}
}
private:
// 用multimap存储定时器,key为触发时间(自动按时间排序)
// 支持多个定时器在同一时间点触发
std::multimap<int64_t, Timer> m_timers;
};
main.cpp
#include "Timer.h"
#include <iostream>
#include <thread>
#include <iomanip>
// 测试任务1:输出当前时间和任务ID
void print_task(int task_id) {
auto now = std::chrono::system_clock::now();
std::time_t now_time = std::chrono::system_clock::to_time_t(now);
std::tm* local_time = std::localtime(&now_time);
std::cout << "[" << std::put_time(local_time, "%H:%M:%S") << "] "
<< "Task " << task_id << " executed" << std::endl;
}
// 测试任务2:有限次数执行,用于验证重复次数功能
void limited_task() {
static int count = 0;
count++;
std::cout << "Limited task executed " << count << " times" << std::endl;
}
// 测试任务3:用于演示定时器可以调用不同类型的函数(带返回值的函数也可适配)
int count_task(int increment) {
static int total = 0;
total += increment;
std::cout << "Count task: total = " << total << std::endl;
return total;
}
int main() {
std::cout << "Timer system test started. Press Ctrl+C to exit." << std::endl;
TimerManager manager;
// 1. 无限循环任务:每1秒执行一次
manager.schedule(1000, print_task, 1);
// 2. 有限次数任务:每2秒执行一次,共执行3次
manager.schedule(2000, 3, limited_task);
// 3. 带参数和返回值的任务:每3秒执行一次,无限循环
// 注意:虽然原函数有返回值,但定时器会忽略返回值
manager.schedule(3000, count_task, 5);
// 4. 短周期任务:每500毫秒执行一次,用于测试高频任务
manager.schedule(500, print_task, 4);
// 主线程循环调用update()检查定时器
// 实际应用中可以根据需要调整检查间隔(权衡精度和CPU占用)
while (true) {
manager.update();
// 每10毫秒检查一次,保证定时精度
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
return 0;
}
这个定时器系统通过 Timer
类封装单个定时任务,结合 TimerManager
类实现多任务集中管理,整体设计简洁且功能完整。以下从核心设计、优势、局限性及改进方向三个维度进行分析:
一、核心设计与工作原理
1. 类职责划分
-
Timer
类:封装单个定时任务的核心信息,包括:- 下一次触发时间(
m_time
,毫秒级时间戳); - 任务周期(
m_period
,毫秒); - 剩余执行次数(
m_repeat
,-1 表示无限循环); - 回调函数(
m_func
,通过std::function
存储任意参数的函数)。 - 核心方法:
callback()
绑定回调与参数并初始化首次触发时间;on_timer()
执行回调并更新下一次触发时间。
- 下一次触发时间(
-
TimerManager
类:管理所有定时器,提供任务调度与执行能力:- 用
std::multimap<int64_t, Timer>
存储定时器(key
为触发时间,自动按时间排序); schedule()
方法用于添加定时任务(支持无限 / 有限次数);update()
方法定期检查并执行到期任务(触发时间 ≤ 当前时间)。
- 用
2. 工作流程
- 任务添加:通过
TimerManager::schedule()
创建Timer
对象,绑定回调函数、设置周期和重复次数,然后按触发时间插入multimap
。 - 任务执行:外部定期调用
update()
,遍历multimap
中所有到期任务(按时间顺序),执行回调后:- 若任务需重复(
m_repeat ≠ 0
),更新其触发时间并重新插入multimap
; - 若任务已结束(
m_repeat = 0
),直接从容器中移除。
- 若任务需重复(
二、设计优势
-
高效的任务管理
利用std::multimap
的自动排序特性(按触发时间升序),update()
可快速定位并处理所有到期任务(遍历到第一个未到期任务即可终止),时间复杂度为O(n)
(n
为到期任务数),适合多任务场景。 -
灵活的任务支持
- 支持无限循环任务(
m_repeat = -1
)和有限次数任务(m_repeat > 0
); - 通过模板 +
std::bind
+ 完美转发,支持任意参数的回调函数(普通函数、lambda、成员函数等)。
- 支持无限循环任务(
-
健壮的边界处理
schedule()
方法包含参数校验(周期必须 > 0,重复次数只能是 -1 或 > 0),避免无效输入;- 处理
multimap
迭代器失效问题:通过保存定时器副本,避免删除元素后访问失效迭代器。
-
轻量的实现
无复杂依赖,仅使用 C++ 标准库(std::thread
、std::chrono
、std::multimap
等),易于集成到各类项目中。
三、局限性与改进方向
-
依赖主动调用
update()
定时器不会自动检查到期任务,需外部(如主线程循环)定期调用update()
。若调用间隔过长,会导致任务触发延迟(最大延迟 =update()
调用间隔)。
改进:让TimerManager
启动独立线程,在内部循环调用update()
(可设置检查间隔,如 10ms),减少外部依赖。 -
线程安全性缺失
多线程环境下,同时调用schedule()
(写操作)和update()
(读写操作)会导致multimap
数据竞争,引发未定义行为。
改进:添加互斥锁(std::mutex
),保护m_timers
的所有读写操作(schedule()
、update()
中加锁)。 -
定时精度受回调执行时间影响
任务周期是 “相对周期”(下一次触发时间 = 本次执行结束时间 + 周期),若回调函数执行耗时较长(如超过周期),会导致实际间隔远超预期。
改进:采用 “绝对周期” 计算下一次触发时间(m_time = 首次触发时间 + n * 周期
),补偿回调耗时。 -
缺少任务取消机制
一旦任务被schedule()
调度,无法中途取消(只能等待执行完毕),灵活性不足。
改进:让schedule()
返回唯一标识符(如Timer*
或自定义 ID),新增cancel(id)
方法从m_timers
中移除对应任务。 -
回调异常可能崩溃整个管理器
若回调函数抛出未捕获的异常,会直接导致update()
中断,影响后续任务执行。
改进:在on_timer()
中添加try-catch
块,捕获异常并记录日志(或其他处理),避免扩散。 -
任务执行阻塞问题
所有任务在update()
调用线程中执行,若某个回调函数阻塞(如死循环),会导致整个定时器系统卡住。
改进:为每个任务创建独立线程执行回调(适合长任务),或限制回调执行时间(超时则强制终止)。
四、适用场景总结
该定时器系统适合 单线程环境、对定时精度要求不高、任务执行时间短 的场景(如简单的 UI 刷新、状态检查等)。若用于多线程、高精度或复杂任务调度(如服务器定时任务),需针对上述局限性进行优化。
总体而言,其核心设计思路清晰,通过集中式管理和有序容器实现了高效的定时任务调度,是一个基础但可用的定时器框架。