实现支持纳秒级精度的时间引擎(C++)

复制代码
## 前言

在游戏服务器开发中,定时器是一个核心组件,用于处理各种定时任务,如心跳检测、超时处理、定时刷新等。本文将介绍如何在 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  
相关推荐
Keep learning!2 小时前
PCA主成分分析学习
学习·算法
专注VB编程开发20年2 小时前
CUDA实现随机切割算法,显卡多线程计算
算法·cuda
2301_788770552 小时前
OJ模拟4
算法
NAGNIP3 小时前
一文搞懂CNN经典架构-AlexNet!
人工智能·算法
2401_878530213 小时前
自定义内存布局控制
开发语言·c++·算法
专注VB编程开发20年3 小时前
PNG、GIF透明游戏角色人物输出一张图片技巧,宽度高度读取
算法
CoderCodingNo3 小时前
【CSP】CSP-J 2025真题 | 异或和 luogu-P14359 (相当于GESP六级水平)
算法
keep intensify4 小时前
打家劫舍3
算法·深度优先
历程里程碑4 小时前
Protobuf 环境搭建:Windows 与 Linux 系统安装教程
linux·运维·数据结构·windows·线性代数·算法·矩阵