背景
如图,写一个定时器。
以rocksdb里面的默认定时器为基础,修改成一个通用的linux下定时器。
接口
- 指定一个任务,m微秒后执行一次。
- 指定一个任务,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,堆的有序性被静默破坏,后果是:
- 任务执行顺序错乱
- 部分任务长时间无法被调度
注释中虽提到"可能破坏堆序",但将其定性为"权衡"是不准确的------这是一个设计缺陷,没有任何缓解措施。
新版本的问题
实现有的复杂