从 RocksDB 定时器出发:手写一个通用的 Linux 高精度定时器

背景

如图,写一个定时器。

以rocksdb里面的默认定时器为基础,修改成一个通用的linux下定时器。

接口

  1. 指定一个任务,m微秒后执行一次。
  2. 指定一个任务,m微秒后执行一次,之后每隔n微秒再执行1次

简易版代码

cpp 复制代码
#pragma once

#include <iostream>
#include <functional>
#include <memory>
#include <queue>
#include <unordered_map>
#include <utility>
#include <vector>
#include <chrono>
#include <mutex>
#include <condition_variable>
#include <thread>
#include <cassert>

namespace common {

// 通用定时器类(简易版)
//
// 设计原则:
// - 原地更新:Add 同名任务时不创建新对象,直接修改现有对象
// - 代码简洁:结构轻量,适合任务不频繁更新的场景
//
// 使用方法:
// 1. 创建 Timer 实例
// 2. 调用 Start() 启动定时器线程
// 3. 调用 Add() 添加任务(指定函数名、初始延迟、重复间隔)
// 4. 调用 Shutdown() 关闭定时器
//
// 注意:
// - 原地更新会破坏堆序,可能导致任务延迟执行(但不会丢失)
// - 适用于任务不频繁更新的场景
// - repeat_every_us == 0 表示只执行一次

class Timer {
public:
    explicit Timer()
        : should_run_(false) {}

    // 禁止拷贝和移动
    Timer(const Timer&) = delete;
    Timer& operator=(const Timer&) = delete;
    Timer(Timer&&) = delete;
    Timer& operator=(Timer&&) = delete;

    ~Timer() { Shutdown(); }

    // 添加一个新任务
    //
    // 参数:
    //   fn:             要执行的函数
    //   fn_name:        函数名(必须唯一,否则新任务会覆盖旧任务)
    //   start_after_us: 初始延迟(微秒)
    //   repeat_every_us:重复间隔(微秒),0 表示不重复
    //
    // 例子:
    //   // 5ms 后执行一次
    //   timer.Add([] { std::cout << "Hello" << std::endl; },
    //             "task1", 5000, 0);
    //
    //   // 5ms 后开始,之后每隔 5ms 执行一次
    //   timer.Add([] { std::cout << "Periodic" << std::endl; },
    //             "task2", 5000, 5000);
    void Add(std::function<void()> fn,
             const std::string& fn_name,
             uint64_t start_after_us,
             uint64_t repeat_every_us) {
        std::lock_guard<std::mutex> lock(mutex_);

        auto it = map_.find(fn_name);
        if (it == map_.end()) {
            // 新任务:创建并入堆
            auto fn_info = std::make_shared<FunctionInfo>(
                std::move(fn), fn_name,
                NowMicros() + start_after_us, repeat_every_us);
            heap_.push(fn_info);
            map_[fn_name] = fn_info;
        } else {
            // 已存在:原地更新,不创建新对象
            // 注意:直接修改堆内元素会破坏堆序,可能导致任务延迟执行
            it->second->fn = std::move(fn);
            it->second->valid = true;
            it->second->next_run_time_us = NowMicros() + start_after_us;
            it->second->repeat_every_us = repeat_every_us;
        }
        cond_var_.notify_all();
    }

    void Add(std::function<void()> fn,
             const std::string& fn_name,
             uint64_t start_after_us) {
        Add(std::move(fn), fn_name, start_after_us, 0);
    }

    // 取消指定名称的任务
    // 立即返回,不等待正在执行的任务完成
    void Cancel(const std::string& fn_name) {
        std::lock_guard<std::mutex> lock(mutex_);
        auto it = map_.find(fn_name);
        if (it != map_.end()) {
            it->second->Cancel();
            // 唤醒 Run() 线程,使其及时发现任务已取消,
            // 避免在 wait_for 中继续等待已无效任务的触发时间
            cond_var_.notify_all();
        }
    }

    // 取消所有任务
    void CancelAll() {
        std::lock_guard<std::mutex> lock(mutex_);
        for (auto& elem : map_) {
            elem.second->Cancel();
        }
        cond_var_.notify_all();
    }

    bool Start() {
        std::lock_guard<std::mutex> lock(mutex_);
        if (should_run_) {
            return false;
        }
        should_run_ = true;
        thread_ = std::thread(&Timer::Run, this);
        return true;
    }

    // 注意:会等待正在执行的任务完成后再清理
    bool Shutdown() {
        {
            std::lock_guard<std::mutex> lock(mutex_);
            if (!should_run_) {
                return false;
            }
            should_run_ = false;
            for (auto& elem : map_) {
                elem.second->Cancel();
            }
            cond_var_.notify_all();
        }

        // 等待线程退出(包括正在执行的任务完成)
        if (thread_.joinable()) {
            thread_.join();
        }

        // 线程退出后,安全清理
        {
            std::lock_guard<std::mutex> lock(mutex_);
            while (!heap_.empty()) {
                heap_.pop();
            }
            map_.clear();
        }
        return true;
    }

    // 检查是否有待执行的有效任务
    bool HasPendingTask() const {
        std::lock_guard<std::mutex> lock(mutex_);
        for (const auto& it : map_) {
            if (it.second->IsValid()) {
                return true;
            }
        }
        return false;
    }

private:
    // 获取当前时间(微秒)
    static uint64_t NowMicros() {
        auto now = std::chrono::steady_clock::now();
        return std::chrono::duration_cast<std::chrono::microseconds>(
            now.time_since_epoch()).count();
    }

    // 任务信息结构体
    struct FunctionInfo {
        // 实际要执行的函数
        std::function<void()> fn;
        // 函数名(唯一标识)
        std::string name;
        // 下次执行时间(微秒)
        uint64_t next_run_time_us;
        // 重复间隔(微秒)
        uint64_t repeat_every_us;
        // 是否有效(被取消后会设为 false)
        bool valid;

        FunctionInfo(std::function<void()>&& _fn, std::string _name,
                     const uint64_t _next_run_time_us, uint64_t _repeat_every_us)
            : fn(std::move(_fn)),
              name(std::move(_name)),
              next_run_time_us(_next_run_time_us),
              repeat_every_us(_repeat_every_us),
              valid(true) {}

        void Cancel() {
            valid = false;
        }

        [[nodiscard]] bool IsValid() const {
            return valid;
        }
    };

    // 小顶堆比较函数(next_run_time_us 越小越优先)
    struct RunTimeOrder {
        bool operator()(const std::shared_ptr<FunctionInfo>& f1,
                        const std::shared_ptr<FunctionInfo>& f2) const {
            return f1->next_run_time_us > f2->next_run_time_us;
        }
    };

    // 执行单个任务
    void ExecuteTask(std::unique_lock<std::mutex>& lock,
                     std::shared_ptr<FunctionInfo> current_fn) {
        // 复制函数对象和任务信息,避免解锁后改变
        std::function<void()> fn = current_fn->fn;
        std::string fn_name = current_fn->name;
        bool is_periodic = current_fn->repeat_every_us > 0;

        // 解锁执行任务
        lock.unlock();
        try {
            fn();
        } catch (const std::exception& e) {
            std::cerr << "Timer task exception: " << e.what() << std::endl;
        } catch (...) {
            std::cerr << "Timer task unknown exception" << std::endl;
        }
        lock.lock();

        // 修复 Bug1:不能无条件 pop,需先确认堆顶仍是当前任务
        // 任务执行期间(解锁状态)其他线程可能通过 Add() 修改了堆结构,
        // 若堆顶已不是 current_fn,跳过 pop,残留项等浮到堆顶时由 Run() 清理
        if (!heap_.empty() && heap_.top() == current_fn) {
            heap_.pop();
        }

        // 检查是否被取消
        if (!current_fn->IsValid()) {
            map_.erase(fn_name);
            return;
        }

        // 周期任务:重新调度
        if (is_periodic && should_run_) {
            current_fn->next_run_time_us = NowMicros() + current_fn->repeat_every_us;
            heap_.push(current_fn);
        } else {
            // 一次性任务或定时器关闭:从 map 中清理
            map_.erase(fn_name);
        }
    }

    // 定时器主循环
    void Run() {
        std::unique_lock<std::mutex> lock(mutex_);

        while (should_run_) {
            if (heap_.empty()) {
                cond_var_.wait(lock, [this]() { return !should_run_ || !heap_.empty(); });
                continue;
            }

            auto current_fn = heap_.top();

            if (!current_fn->IsValid()) {
                heap_.pop();
                map_.erase(current_fn->name);
                continue;
            }

            // 等待到执行时间
            auto now = NowMicros();
            if (now < current_fn->next_run_time_us) {
                auto duration_us = current_fn->next_run_time_us - now;
                cond_var_.wait_for(lock, std::chrono::microseconds(duration_us));
                continue;
            }

            ExecuteTask(lock, current_fn);
        }
    }

    // 成员变量
    mutable std::mutex mutex_;
    std::condition_variable cond_var_;
    std::thread thread_;
    // 由 mutex_ 保护,condition_variable 的谓词变量不应使用 atomic
    bool should_run_;

    // 小顶堆,存储指向 FunctionInfo 的指针
    // 按执行时间排序,堆顶是最早要执行的任务
    std::priority_queue<std::shared_ptr<FunctionInfo>,
                        std::vector<std::shared_ptr<FunctionInfo>>,
                        RunTimeOrder> heap_;

    // map 从函数名映射到函数对象
    // 负责内存管理(使用 shared_ptr)
    std::unordered_map<std::string, std::shared_ptr<FunctionInfo>> map_;
};

} // namespace common

复杂版

cpp 复制代码
#pragma once

#include <iostream>
#include <functional>
#include <memory>
#include <queue>
#include <unordered_map>
#include <utility>
#include <vector>
#include <chrono>
#include <mutex>
#include <condition_variable>
#include <thread>
#include <cassert>

namespace common {

// 通用定时器类,用于处理周期性任务
//
// 特性:
// - 支持一次性任务和周期性任务
// - 使用小顶堆管理任务队列
// - 单线程执行所有任务(避免并发问题)
// - 支持任务的添加和取消
//
// 使用方法:
// 1. 创建 Timer 实例
// 2. 调用 Start() 启动定时器线程
// 3. 调用 Add() 添加任务(指定函数名、初始延迟、重复间隔)
// 4. 调用 Shutdown() 关闭定时器
//
// 注意:
// - Start() 和 Shutdown() 不是线程安全的,调用者需要串行调用
// - 长时间运行的任务应该使用独立的线程池
// - repeat_every_us == 0 表示只执行一次
//
// 实现细节:
// - 使用小顶堆跟踪下一个要执行的定时任务
// - 使用 map 从函数名映射到函数对象
// - 使用条件变量等待,CPU 占用低

class Timer {
public:
    explicit Timer()
        : should_run_(false) {}

    // 禁止拷贝和移动
    Timer(const Timer&) = delete;
    Timer& operator=(const Timer&) = delete;
    Timer(Timer&&) = delete;
    Timer& operator=(Timer&&) = delete;

    ~Timer() { Shutdown(); }

    // 添加一个新任务
    //
    // 参数:
    // fn:             要执行的函数
    // fn_name:        函数名(必须唯一,否则新任务会覆盖旧任务)
    // start_after_us: 初始延迟(微秒)
    // repeat_every_us:重复间隔(微秒),0 表示不重复
    //
    // 例子:
    // // 5ms 后执行一次
    // timer.Add([] { std::cout << "Hello" << std::endl; },
    //           "task1", 5000, 0);
    //
    // // 5ms 后开始,之后每隔 5ms 执行一次
    // timer.Add([] { std::cout << "Periodic" << std::endl; },
    //           "task2", 5000, 5000);
    void Add(std::function<void()> fn,
             const std::string& fn_name,
             uint64_t start_after_us,
             uint64_t repeat_every_us) {
        auto fn_info = std::make_shared<FunctionInfo>(
            std::move(fn), fn_name,
            NowMicros() + start_after_us, repeat_every_us);

        {
            std::lock_guard<std::mutex> lock(mutex_);
            if (map_.count(fn_name)) {
                map_[fn_name]->Cancel();
            }
            heap_.push(fn_info);
            map_[fn_name] = fn_info;
        }
        cond_var_.notify_all();
    }

    void Add(std::function<void()> fn,
             const std::string& fn_name,
             uint64_t start_after_us) {
        Add(std::move(fn), fn_name, start_after_us, 0);
    }

    // 取消指定名称的任务
    // 立即返回,不等待正在执行的任务完成
    void Cancel(const std::string& fn_name) {
        std::unique_lock<std::mutex> lock(mutex_);
        auto it = map_.find(fn_name);
        if (it != map_.end()) {
            it->second->Cancel();
            // 唤醒 Run() 线程,使其及时发现任务已取消,
            // 避免在 wait_for 中继续等待已无效任务的触发时间
            cond_var_.notify_all();
        }
    }

    // 取消所有任务
    void CancelAll() {
        std::unique_lock<std::mutex> lock(mutex_);
        CancelAllWithLock();
    }

    bool Start() {
        std::lock_guard<std::mutex> lock(mutex_);
        if (should_run_) {
            return false;
        }
        should_run_ = true;
        thread_ = std::thread(&Timer::Run, this);
        return true;
    }

    // 注意:会等待正在执行的任务完成后再清理
    bool Shutdown() {
        {
            std::unique_lock<std::mutex> lock(mutex_);
            if (!should_run_) {
                return false;
            }
            should_run_ = false;
            CancelAllWithLock();
        }

        // 等待线程退出(包括正在执行的任务完成)
        if (thread_.joinable()) {
            thread_.join();
        }

        // 线程退出后,安全清理
        {
            std::lock_guard<std::mutex> lock(mutex_);
            while (!heap_.empty()) {
                heap_.pop();
            }
            map_.clear();
        }
        return true;
    }

    // 检查是否有待执行的有效任务
    bool HasPendingTask() const {
        std::lock_guard<std::mutex> lock(mutex_);
        for (const auto& it : map_) {
            if (it.second->IsValid()) {
                return true;
            }
        }
        return false;
    }

private:
    // 获取当前时间(微秒)
    static uint64_t NowMicros() {
        auto now = std::chrono::steady_clock::now();
        return std::chrono::duration_cast<std::chrono::microseconds>(
            now.time_since_epoch()).count();
    }

    // 任务信息结构体
    // 跟踪函数的下次执行时间和频率
    struct FunctionInfo {
        // 实际要执行的函数
        std::function<void()> fn;
        // 函数名(唯一标识)
        std::string name;
        // 下次执行时间(微秒)
        uint64_t next_run_time_us;
        // 重复间隔(微秒)
        uint64_t repeat_every_us;
        // 是否有效(被取消后会设为 false)
        bool valid;

        FunctionInfo(std::function<void()>&& _fn, std::string _name,
                     const uint64_t _next_run_time_us, uint64_t _repeat_every_us)
            : fn(std::move(_fn)),
              name(std::move(_name)),
              next_run_time_us(_next_run_time_us),
              repeat_every_us(_repeat_every_us),
              valid(true) {}

        void Cancel() {
            valid = false;
        }

        [[nodiscard]] bool IsValid() const {
            return valid;
        }
    };

    // 小顶堆比较函数(next_run_time_us 越小越优先)
    struct RunTimeOrder {
        bool operator()(const std::shared_ptr<FunctionInfo>& f1,
                        const std::shared_ptr<FunctionInfo>& f2) const {
            return f1->next_run_time_us > f2->next_run_time_us;
        }
    };

    // 执行单个任务
    void ExecuteTask(std::unique_lock<std::mutex>& lock,
                     std::shared_ptr<FunctionInfo> current_fn) {
        assert(current_fn && current_fn->IsValid());

        // 复制函数对象和任务信息,避免解锁后改变
        std::function<void()> fn = current_fn->fn;
        std::string fn_name = current_fn->name;
        bool is_periodic = current_fn->repeat_every_us > 0;

        // 解锁执行任务
        lock.unlock();
        try {
            fn();
        } catch (const std::exception& e) {
            std::cerr << "Timer task exception: " << e.what() << std::endl;
        } catch (...) {
            std::cerr << "Timer task unknown exception" << std::endl;
        }
        lock.lock();

        if (!current_fn->IsValid()) {
            // 尝试清理 map(只有当它还是 map 中的当前任务时才删,防止误删新任务)
            auto it = map_.find(fn_name);
            if (it != map_.end() && it->second == current_fn) {
                map_.erase(it);
            }
            // 堆中的残留项将由 Run 循环在将来它变为 top 时清理
            return;
        }

        bool removed_from_heap = false;
        if (!heap_.empty() && heap_.top() == current_fn) {
            heap_.pop();
            removed_from_heap = true;
        }

        // 如果是周期性任务且定时器仍在运行,重新调度
        if (is_periodic && should_run_) {
            if (removed_from_heap) {
                current_fn->next_run_time_us = NowMicros() + current_fn->repeat_every_us;
                heap_.push(current_fn);
                return;
            }
            // removed_from_heap == false:说明该任务已不在堆顶(被其他路径处理),
            // 无法重新调度,降级为清理,防止 map_ 泄漏
        }

        // 一次性任务执行完毕、定时器正在关闭、或周期任务无法重新入堆,从 map_ 中清理
        auto it = map_.find(fn_name);
        if (it != map_.end() && it->second == current_fn) {
            map_.erase(it);
        }
    }

    // 定时器主循环
    void Run() {
        std::unique_lock<std::mutex> lock(mutex_);

        while (should_run_) {
            if (heap_.empty()) {
                cond_var_.wait(lock, [this]() { return !should_run_ || !heap_.empty(); });
                continue;
            }

            std::shared_ptr<FunctionInfo> current_fn = heap_.top();
            auto it = map_.find(current_fn->name);
            if (it == map_.end() || it->second != current_fn) {
                heap_.pop();
                continue;
            }

            if (!current_fn->IsValid()) {
                heap_.pop();
                map_.erase(it);
                continue;
            }

            // 等待到执行时间
            auto now = NowMicros();
            if (now < current_fn->next_run_time_us) {
                auto duration_us = current_fn->next_run_time_us - now;
                cond_var_.wait_for(lock, std::chrono::microseconds(duration_us));
                continue;
            }

            ExecuteTask(lock, current_fn);
        }
    }

    void CancelAllWithLock() {
        // 将所有任务标记为无效,不立即清理
        // 避免与正在执行的任务冲突
        // 清理由 Run() 循环自动处理,或者 Shutdown() 时统一清理
        for (auto& elem : map_) {
            elem.second->Cancel();
        }
        // 无论如何都要通知,确保 Run() 线程能检查 should_run_ 状态
        cond_var_.notify_all();
    }

    // 成员变量
    mutable std::mutex mutex_;
    std::condition_variable cond_var_;
    std::thread thread_;
    // 由 mutex_ 保护,condition_variable 的谓词变量不应使用 atomic
    bool should_run_;

    // 小顶堆,存储指向 FunctionInfo 的指针
    // 按执行时间排序,堆顶是最早要执行的任务
    std::priority_queue<std::shared_ptr<FunctionInfo>,
                        std::vector<std::shared_ptr<FunctionInfo>>,
                        RunTimeOrder> heap_;

    // map 从函数名映射到函数对象
    // 负责内存管理(使用 shared_ptr)
    std::unordered_map<std::string, std::shared_ptr<FunctionInfo>> map_;
};

} // namespace common

简易版的个严重 Bug

Bug :原地更新破坏堆序

std::priority_queue 底层是数组堆,不支持对已入堆元素的键值修改
Add() 同名任务时直接修改现有元素的 next_run_time_us,堆的有序性被静默破坏,后果是:

  • 任务执行顺序错乱
  • 部分任务长时间无法被调度

注释中虽提到"可能破坏堆序",但将其定性为"权衡"是不准确的------这是一个设计缺陷,没有任何缓解措施。

新版本的问题

实现有的复杂

相关推荐
旺仔.2912 小时前
线程安全 详解
linux·计算机网络·安全
大傻^3 小时前
Spring AI 2.0 MCP 协议实战:Model Context Protocol SDK 与多服务器编排
服务器·人工智能·spring
追风林3 小时前
idea支持本地 的 服务器 远程debug
java·服务器·intellij-idea
yuuki2332334 小时前
【Linux】开发工具链全解析:从 apt 到 gdb
linux·运维·服务器
wangjialelele4 小时前
C++11、C++14、C++17、C++20新特性解析(一)
linux·c语言·开发语言·c++·c++20·visual studio
蓝队云计算4 小时前
蓝队云揭秘:如何利用云服务器高效养殖龙虾OpenClaw?
运维·服务器·人工智能·云服务器·openclaw
ZTLJQ4 小时前
驾驭高并发:Python协程与 async/await 完全解析
服务器·数据库·python
²º²²এ松4 小时前
vs code连接ubuntu esp项目
linux·数据库·ubuntu
浪客灿心4 小时前
Linux进程信号
linux