Timer

基于多线程的定时器

代码

#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::forwardstd::bind实现参数绑定)。
  • 逻辑:
    1. 检查定时器是否已运行(m_active),避免重复启动。
    2. 初始化周期、任务函数,标记m_active=true
    3. 启动新线程,根据m_repeat执行任务:
      • 无限循环(m_repeat < 0):持续睡眠m_period毫秒后执行任务,直到m_activefalse
      • 有限次数(m_repeat > 0):执行m_repeat次后自动停止。
    4. 线程最终被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()调用时线程已detachjoinable()返回falsejoin()不会执行,但线程可能仍在后台运行(直到自然结束),存在资源释放延迟风险。
  • 多次调用start()可能导致线程资源泄漏:若前一次detach的线程尚未结束,新线程又被创建,旧线程可能仍在运行。
(2) 定时精度问题
  • 实际执行周期 = 睡眠时间(m_period) + 任务函数执行时间。若任务函数执行耗时较长,会导致后续执行延迟,无法保证严格的周期性。
(3) 重复次数设计缺陷
  • m_repeatstart()中不会重置,若同一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. 工作流程

  1. 任务添加:通过 TimerManager::schedule() 创建 Timer 对象,绑定回调函数、设置周期和重复次数,然后按触发时间插入 multimap
  2. 任务执行:外部定期调用 update(),遍历 multimap 中所有到期任务(按时间顺序),执行回调后:
    • 若任务需重复(m_repeat ≠ 0),更新其触发时间并重新插入 multimap
    • 若任务已结束(m_repeat = 0),直接从容器中移除。

二、设计优势

  1. 高效的任务管理
    利用 std::multimap 的自动排序特性(按触发时间升序),update() 可快速定位并处理所有到期任务(遍历到第一个未到期任务即可终止),时间复杂度为 O(n)n 为到期任务数),适合多任务场景。

  2. 灵活的任务支持

    • 支持无限循环任务(m_repeat = -1)和有限次数任务(m_repeat > 0);
    • 通过模板 + std::bind + 完美转发,支持任意参数的回调函数(普通函数、lambda、成员函数等)。
  3. 健壮的边界处理

    • schedule() 方法包含参数校验(周期必须 > 0,重复次数只能是 -1 或 > 0),避免无效输入;
    • 处理 multimap 迭代器失效问题:通过保存定时器副本,避免删除元素后访问失效迭代器。
  4. 轻量的实现
    无复杂依赖,仅使用 C++ 标准库(std::threadstd::chronostd::multimap 等),易于集成到各类项目中。

三、局限性与改进方向

  1. 依赖主动调用 update()
    定时器不会自动检查到期任务,需外部(如主线程循环)定期调用 update()。若调用间隔过长,会导致任务触发延迟(最大延迟 = update() 调用间隔)。
    改进:让 TimerManager 启动独立线程,在内部循环调用 update()(可设置检查间隔,如 10ms),减少外部依赖。

  2. 线程安全性缺失
    多线程环境下,同时调用 schedule()(写操作)和 update()(读写操作)会导致 multimap 数据竞争,引发未定义行为。
    改进:添加互斥锁(std::mutex),保护 m_timers 的所有读写操作(schedule()update() 中加锁)。

  3. 定时精度受回调执行时间影响
    任务周期是 “相对周期”(下一次触发时间 = 本次执行结束时间 + 周期),若回调函数执行耗时较长(如超过周期),会导致实际间隔远超预期。
    改进:采用 “绝对周期” 计算下一次触发时间(m_time = 首次触发时间 + n * 周期),补偿回调耗时。

  4. 缺少任务取消机制
    一旦任务被 schedule() 调度,无法中途取消(只能等待执行完毕),灵活性不足。
    改进:让 schedule() 返回唯一标识符(如 Timer* 或自定义 ID),新增 cancel(id) 方法从 m_timers 中移除对应任务。

  5. 回调异常可能崩溃整个管理器
    若回调函数抛出未捕获的异常,会直接导致 update() 中断,影响后续任务执行。
    改进:在 on_timer() 中添加 try-catch 块,捕获异常并记录日志(或其他处理),避免扩散。

  6. 任务执行阻塞问题
    所有任务在 update() 调用线程中执行,若某个回调函数阻塞(如死循环),会导致整个定时器系统卡住。
    改进:为每个任务创建独立线程执行回调(适合长任务),或限制回调执行时间(超时则强制终止)。

四、适用场景总结

该定时器系统适合 单线程环境对定时精度要求不高任务执行时间短 的场景(如简单的 UI 刷新、状态检查等)。若用于多线程、高精度或复杂任务调度(如服务器定时任务),需针对上述局限性进行优化。

总体而言,其核心设计思路清晰,通过集中式管理和有序容器实现了高效的定时任务调度,是一个基础但可用的定时器框架。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值