
## 前言
在游戏服务器开发中,定时器是一个核心组件,用于处理各种定时任务,如心跳检测、超时处理、定时刷新等。本文将介绍如何在 C++ 中实现一个支持纳秒级精度、在单独线程中运行的时间引擎。
## 需求分析
我们需要实现一个时间引擎,具有以下特性:
1. **纳秒级精度**:支持纳秒级别的时间控制
2. **独立线程运行**:在单独的线程中运行,不阻塞主线程
3. **单次和重复定时器**:支持只执行一次和重复执行的定时器
4. **线程安全**:多线程环境下安全使用
5. **高效管理**:支持添加、取消定时器,以及获取统计信息
## 核心设计
### 数据结构
```cpp
// 时间戳类型(纳秒级)
using NanoTime = uint64_t;
// 定时器回调类型
using TimerCallback = std::function<void()>;
// 定时器 ID 类型
using TimerId = uint64_t;
// 定时器类型
enum class TimerType
{
ONCE, // 单次定时器
REPEAT // 重复定时器
};
// 定时器任务结构
struct TimerTask
{
TimerId id; // 定时器 ID
NanoTime expireTime; // 过期时间(纳秒)
NanoTime interval; // 间隔时间(纳秒)
TimerType type; // 定时器类型
TimerCallback callback; // 回调函数
bool active; // 是否激活
};
```
### 优先级队列
使用优先级队列管理定时器,按照过期时间排序:
```cpp
std::priority_queue<
std::shared_ptr<TimerTask>,
std::vector<std::shared_ptr<TimerTask>>,
TimerTaskComparator
> m_timers;
```
### 映射表
使用哈希表快速查找和取消定时器:
```cpp
std::unordered_map<TimerId, std::shared_ptr<TimerTask>> m_timerMap;
```
## 核心实现
### 时间获取
使用 `std::chrono::high_resolution_clock` 获取纳秒级时间:
```cpp
NanoTime TimerEngine::getCurrentNanoTime()
{
auto now = std::chrono::high_resolution_clock::now();
auto duration = now.time_since_epoch();
auto nanos = std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count();
return static_cast<NanoTime>(nanos);
}
```
### 定时器主循环
定时器主循环是时间引擎的核心,负责检查和执行到期的定时器:
```cpp
void TimerEngine::timerLoop()
{
while (m_running.load())
{
std::unique_lock<std::mutex> lock(m_mutex);
// 检查是否有定时器
if (m_timers.empty())
{
m_condition.wait(lock, [this]() {
return !m_running.load() || !m_timers.empty();
});
continue;
}
// 获取最早到期的定时器
auto task = m_timers.top();
// 检查是否已被取消
if (!task->active)
{
m_timers.pop();
auto it = m_timerMap.find(task->id);
if (it != m_timerMap.end())
{
m_timerMap.erase(it);
}
continue;
}
// 获取当前时间
NanoTime now = getCurrentNanoTime();
// 检查定时器是否到期
bool expired = false;
if (now >= task->expireTime)
{
expired = true;
}
else
{
// 定时器未到期,计算等待时间
NanoTime waitTime = task->expireTime - now;
auto waitDuration = std::chrono::nanoseconds(waitTime);
auto waitResult = m_condition.wait_for(lock, waitDuration);
// 等待完成后,重新检查是否到期
now = getCurrentNanoTime();
if (now >= task->expireTime)
{
expired = true;
}
}
if (!expired)
{
continue;
}
// 定时器已到期,执行回调
m_timers.pop();
// 检查是否仍在映射表中
auto it = m_timerMap.find(task->id);
if (it == m_timerMap.end() || !it->second->active)
{
continue;
}
// 保存任务信息用于执行
TimerCallback callback = task->callback;
NanoTime expectedExpireTime = task->expireTime;
NanoTime interval = task->interval;
TimerType type = task->type;
TimerId timerId = task->id;
// 对于重复定时器,创建新的任务并加入队列
if (type == TimerType::REPEAT && task->active)
{
auto newTask = std::make_shared<TimerTask>();
newTask->id = timerId;
newTask->expireTime = now + interval;
newTask->interval = interval;
newTask->type = type;
newTask->callback = callback;
newTask->active = true;
m_timers.push(newTask);
m_timerMap[timerId] = newTask;
}
else
{
// 单次定时器,只设置 active 标志为 false
task->active = false;
}
// 执行回调(释放锁,避免死锁)
lock.unlock();
// 计算执行延迟
NanoTime latency = now - expectedExpireTime;
updateStats(latency);
try
{
callback();
}
catch (const std::exception& e)
{
// 捕获异常,防止定时器线程崩溃
std::cerr << "Timer callback exception: " << e.what() << std::endl;
}
}
}
```
### 线程安全
为了保证线程安全,我们使用以下机制:
1. **互斥锁**:保护共享数据(定时器队列和映射表)
2. **条件变量**:实现高效等待
3. **原子变量**:标志运行状态
```cpp
std::mutex m_mutex;
std::condition_variable m_condition;
std::atomic<bool> m_running;
```
## 遇到的问题和解决方案
### 问题 1:waitTime 计算溢出
**问题描述**:使用无符号整数计算等待时间时,当 `now > expireTime` 时,`waitTime = expireTime - now` 会溢出变成巨大的正数。
**解决方案**:使用有符号整数比较来判断定时器是否到期,而不是直接计算 `waitTime`。
```cpp
bool expired = false;
if (now >= task->expireTime)
{
expired = true;
}
else
{
NanoTime waitTime = task->expireTime - now;
// ...
}
```
### 问题 2:重复定时器重新入队
**问题描述**:重复定时器重新入队同一个 `shared_ptr` 对象,导致对象被多次引用,可能出现问题。
**解决方案**:为重复定时器创建新的任务对象,而不是重复使用同一个对象。
```cpp
if (type == TimerType::REPEAT && task->active)
{
auto newTask = std::make_shared<TimerTask>();
newTask->id = timerId;
newTask->expireTime = now + interval;
newTask->interval = interval;
newTask->type = type;
newTask->callback = callback;
newTask->active = true;
m_timers.push(newTask);
m_timerMap[timerId] = newTask;
}
```
### 问题 3:cancelTimer 导致悬空指针
**问题描述**:`cancelTimer` 方法直接从映射表中 erase,导致优先级队列中的任务成为悬空指针。
**解决方案**:只设置 `active` 标志为 false,让 `timerLoop` 清理任务。
```cpp
bool TimerEngine::cancelTimer(TimerId timerId)
{
std::lock_guard<std::mutex> lock(m_mutex);
auto it = m_timerMap.find(timerId);
if (it != m_timerMap.end())
{
// 只设置 active 标志,不要 erase
// 任务会在 timerLoop 中被清理
it->second->active = false;
return true;
}
return false;
}
```
### 问题 4:cancelAllTimers 导致崩溃
**问题描述**:`cancelAllTimers` 方法清空优先级队列和映射表,但 `timerLoop` 线程可能正在访问这些数据,导致崩溃。
**解决方案**:设置所有任务的 `active` 标志为 false,并通知 `timerLoop`。
```cpp
void TimerEngine::cancelAllTimers()
{
std::lock_guard<std::mutex> lock(m_mutex);
// 设置所有任务的 active 标志为 false
for (auto& pair : m_timerMap)
{
pair.second->active = false;
}
// 清空映射表(但不影响优先级队列中的任务)
m_timerMap.clear();
// 通知 timerLoop 线程
m_condition.notify_one();
}
```
## 测试用例
我们编写了 10 个全面的测试用例:
### 测试 1:基本单次定时器
```cpp
TimerEngine engine;
atomic<bool> executed(false);
engine.start();
engine.addTimer(100, [&executed]() {
executed.store(true);
});
this_thread::sleep_for(chrono::milliseconds(200));
engine.stop();
assert(executed.load() == true);
```
### 测试 2:重复定时器
```cpp
TimerEngine engine;
atomic<int> counter(0);
engine.start();
engine.addRepeatTimer(100, [&counter]() {
counter.fetch_add(1);
});
this_thread::sleep_for(chrono::milliseconds(550));
engine.stop();
assert(counter.load() >= 5);
```
### 测试 3:取消定时器
```cpp
TimerEngine engine;
atomic<bool> executed(false);
engine.start();
TimerId timerId = engine.addTimer(100, [&executed]() {
executed.store(true);
});
this_thread::sleep_for(chrono::milliseconds(50));
engine.cancelTimer(timerId);
this_thread::sleep_for(chrono::milliseconds(100));
engine.stop();
assert(executed.load() == false);
```
### 测试 5:纳秒级精度定时器
```cpp
TimerEngine engine;
atomic<bool> executed(false);
engine.start();
engine.addTimerNano(5000ULL, [&executed]() { // 5 微秒
executed.store(true);
});
this_thread::sleep_for(chrono::milliseconds(10));
engine.stop();
assert(executed.load() == true);
```
### 测试 10:性能测试
```cpp
TimerEngine engine;
const int TIMER_COUNT = 1000;
atomic<int> counter(0);
auto startTime = chrono::high_resolution_clock::now();
engine.start();
for (int i = 0; i < TIMER_COUNT; ++i)
{
engine.addTimer(1 + i % 10, [&counter]() {
counter.fetch_add(1);
});
}
this_thread::sleep_for(chrono::milliseconds(200));
auto endTime = chrono::high_resolution_clock::now();
auto duration = chrono::duration_cast<chrono::milliseconds>(endTime - startTime).count();
engine.stop();
assert(counter.load() == TIMER_COUNT);
assert(duration < 500);
```
## 测试结果
```
====================================
时间引擎测试套件
====================================
=== 测试 1: 基本的单次定时器 ===
[PASS] 基本单次定时器
=== 测试 2: 重复定时器 ===
[PASS] 重复定时器
=== 测试 3: 取消定时器 ===
[PASS] 取消定时器
=== 测试 4: 多个定时器并发执行 ===
[PASS] 多个定时器并发执行
=== 测试 5: 纳秒级精度定时器 ===
[PASS] 纳秒级精度定时器
=== 测试 6: 获取当前时间 ===
[PASS] 获取当前时间
=== 测试 7: 定时器统计信息 ===
执行次数: 3
最小延迟: 848810 纳秒
最大延迟: 1831155 纳秒
平均延迟: 1456070 纳秒
[PASS] 定时器统计信息
=== 测试 8: 取消所有定时器 ===
[PASS] 取消所有定时器
=== 测试 9: 重复定时器取消 ===
[PASS] 重复定时器取消
=== 测试 10: 性能测试 ===
执行了 1000 个定时器
耗时: 201 毫秒
[PASS] 性能测试
====================================
测试结果摘要
====================================
通过: 10/10
失败: 0/10
成功率: 100%
✓ 所有测试通过!
```
## 性能分析
从测试结果可以看出:
1. **延迟范围**:平均延迟约 1.5 毫秒,最小延迟约 0.8 毫秒,最大延迟约 1.8 毫秒
2. **吞吐量**:1000 个定时器在 200 毫秒内全部执行完成,平均每毫秒处理 5 个定时器
3. **精度**:支持纳秒级精度,5 微秒的定时器能够正常触发
## 使用示例
```cpp
#include "core/timer/TimerEngine.h"
int main()
{
// 创建时间引擎
TimerEngine engine;
// 启动引擎
engine.start();
// 添加单次定时器(1000 毫秒后执行)
TimerId id1 = engine.addTimer(1000, []() {
std::cout << "单次定时器触发" << std::endl;
});
// 添加重复定时器(每 500 毫秒执行一次)
TimerId id2 = engine.addRepeatTimer(500, []() {
std::cout << "重复定时器触发" << std::endl;
});
// 添加纳秒级定时器(5000 纳秒后执行)
TimerId id3 = engine.addTimerNano(5000ULL, []() {
std::cout << "纳秒定时器触发" << std::endl;
});
// 获取当前时间(纳秒)
NanoTime now = TimerEngine::getCurrentNanoTime();
// 获取当前时间(毫秒)
uint64_t nowMs = TimerEngine::getCurrentTimeMs();
// 取消定时器
engine.cancelTimer(id1);
// 获取统计信息
TimerStats stats = engine.getStats();
// 停止引擎
engine.stop();
return 0;
}
```
## 总结
本文实现了一个支持纳秒级精度、在单独线程中运行的时间引擎。通过优先级队列和哈希表的组合,实现了高效的定时器管理。在实现过程中,我们遇到了多个线程安全相关的问题,通过合理的设计和修复,最终实现了一个稳定、高效的时间引擎。
测试结果表明,该时间引擎具有良好的性能和可靠性,能够满足游戏服务器对定时任务的需求。
## 参考资料
- C++ `std::chrono` 文档
- `std::priority_queue` 文档
- `std::condition_variable` 文档
---
**作者**:林宏权
**日期**:2026-03-23