# timewheel **Repository Path**: deaglebear/timewheel ## Basic Information - **Project Name**: timewheel - **Description**: No description available - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-01-16 - **Last Updated**: 2026-09-01 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # TimeWheel TimeWheel 是一个 C++17、header-only 的分层时间轮库,面向行情回放、仿真测试和大量定时任务调度。 调度核心不拥有线程,也不订阅全局时钟。调用者通过 `Scheduler::AdvanceTo()` 显式推进逻辑时间,因此回放速度与真实时间解耦,执行结果可以稳定复现。实时场景则通过独立的 `RealtimeDriver` 驱动同一个 Scheduler 核心。 ## 特性 - 外部驱动的单调逻辑时间,适合行情回放和模拟时钟。 - 多级时间轮,使用绝对 tick 定位和跨层迁移。 - 超出顶层时间轮范围的任务由 overflow 有序集合承接。 - 支持一次性任务、绝对时间任务和周期任务。 - 支持定时器优先或行情事件优先的同时间戳顺序。 - 大段无任务区间按成本自动选择逐 tick 步进或槽位重建,不需要逐毫秒空转。 - Scheduler 实例之间没有共享线程或全局停止状态。 - 创建、取消任务线程安全;回调异常与其他任务隔离。 - 实时驱动按最早 deadline 睡眠,新创建的更早任务会立即唤醒工作线程。 - 同步 `HandledInstance`(回测)与 `RealtimeInstance`(实时)两种封装,共享同一 Scheduler 核心。 - CMake 和 Conan 2 集成。 ## 架构 ```text 行情回放程序 ── HandledInstance(AdvanceTo / Tick) ──┐ ├── Scheduler ── Hierarchical TimeWheel RealtimeDriver(独立工作线程) ── AdvanceTo(steady_now) ──┘ └── Overflow(按 deadline 有序) ``` Scheduler 是一个被动的逻辑时间状态机。回调同步运行在调用 `AdvanceTo()` 的线程中;实时模式下,这个线程是 RealtimeDriver 的工作线程,回测模式下是调用 HandledInstance 的线程。 `Clock`、`SystemClock` 和 `SteadyClock` 现在只是可选的时间源接口,不保存 Scheduler 回调,也不管理 Scheduler 生命周期。 ## 行情回放快速开始 ```cpp #include "timewheel/timewheel.h" timewheel::Scheduler scheduler( /* tick_ms */ 10, /* start_time_ms */ replay_events.front().timestamp_ms); scheduler.CreateTimerAfter(500, [&] { // Scheduler::Now() 是当前回调的逻辑执行时间。 OnTimeout(scheduler.Now()); }); for (const auto& event : replay_events) { scheduler.AdvanceTo(event.timestamp_ms); ProcessMarketEvent(event); } ``` `AdvanceTo()` 接受的时间必须单调不减。回放 seek、重新开始或切换时间域时使用: ```cpp scheduler.Reset(new_start_time_ms); ``` Reset 会取消并释放全部已有任务。 也可以使用 `HandledInstance` 封装 Scheduler(见下文「回测同步实例」),获得与实时封装一致的异常上报方式,以及按相对步长推进的 `Tick()`。 ## 创建和取消任务 ```cpp // 相对当前逻辑时间,500ms 后执行。 timewheel::TimerId once = scheduler.CreateTimerAfter(500, [] { // ... }); // 在指定逻辑时间执行。 timewheel::TimerId absolute = scheduler.CreateTimerAt(timestamp_ms, [] { // ... }); // 返回是否成功取消了一个仍然活跃的任务。 bool cancelled = scheduler.CancelTimer(once); ``` | API | 说明 | | --- | --- | | `CreateTimerAt(when, task)` | 在绝对逻辑时间执行一次 | | `CreateTimerAfter(delay, task)` | 相对当前调度基准时间延迟执行一次 | | `CreateTimerEvery(interval, task, policy)` | 从当前调度基准时间开始创建周期任务 | | `CancelTimer(id)` | 取消任务,成功返回 `true` | | `AdvanceTo(target, boundary, limit)` | 推进逻辑时间并同步执行到期任务 | | `Reset(start)` | 取消全部任务并建立新时间域 | | `Now()` | 返回当前逻辑时间 | | `NextDeadlineMs()` | 返回最早活跃任务的逻辑时间,无任务时为 `std::nullopt` | | `TimerCount()` | 返回当前活跃任务数量 | `TimerId` 是 64 位无符号整数,0 表示创建失败。目前只有空 `TimerTask` 会返回 0。负延迟、非正周期和时间倒退会抛出 `std::invalid_argument`。 ## 时间精度 所有公开时间参数单位为毫秒。deadline 会向上取整到 tick 网格,任务不会早于请求时间执行。 例如 tick 为 50ms 时: ```text 请求 0ms、10ms、49ms -> 在下一个 50ms tick 执行 请求 50ms -> 在 50ms tick 执行 请求 51ms -> 在 100ms tick 执行 ``` 未关联实时驱动时,调度基准时间就是 `Scheduler::Now()`。回调执行期间,`Scheduler::Now()` 是该任务实际所在的逻辑 tick,而不是本次大幅跳转的最终目标时间,因此回测回调中继续调用 `CreateTimerAfter()` 会基于正确的逻辑执行时间安排新任务。实时驱动运行期间,调度基准时间改为 `max(Scheduler::Now(), steady_clock::now())`,避免驱动休眠时相对定时器使用过期时间。 ## 同时间戳顺序 默认 `Inclusive` 会先执行 deadline 等于目标时间的定时器,再返回给行情程序: ```cpp scheduler.AdvanceTo( event.timestamp_ms, timewheel::DeadlineBoundary::Inclusive); ProcessMarketEvent(event); ``` 如果行情事件需要先于同时间戳定时器处理: ```cpp scheduler.AdvanceTo( event.timestamp_ms, timewheel::DeadlineBoundary::Exclusive); ProcessMarketEvent(event); scheduler.AdvanceTo( event.timestamp_ms, timewheel::DeadlineBoundary::Inclusive); ``` 这种两阶段调用允许策略明确决定同一时间戳的市场事件和定时任务顺序。 ## 周期任务 ```cpp auto timer_id = scheduler.CreateTimerEvery( 100, [] { /* task */ }, timewheel::RepeatPolicy::FixedRateCatchUp); ``` 支持两种策略: - `FixedRateCatchUp`:保持原始节奏。时间从 50ms 跳到 350ms 时,100、200、300ms 的任务都会执行,适合严格模拟。 - `FixedRateSkip`:大幅跳时只执行一次,下一次直接安排到跳转目标之后,适合避免补执行风暴。 `AdvanceTo()` 默认最多执行 1,000,000 个回调。达到严格上限时,未执行的同 tick 任务会保留,`AdvanceResult::reached_target` 为 `false`;使用同一个目标时间再次调用即可继续推进。 ## AdvanceResult 和异常 ```cpp auto result = scheduler.AdvanceTo(target_time_ms); if (!result.reached_target) { // 达到回调保护上限,可再次推进到相同目标。 } for (const auto& error : result.callback_errors) { try { std::rethrow_exception(error); } catch (const std::exception& ex) { ReportTimerError(ex.what()); } } ``` `AdvanceResult` 包含: - `callbacks_executed`:本次实际执行的回调数量。 - `reached_target`:是否已经推进到目标时间。 - `current_time_ms`:推进结束时的逻辑时间。 - `callback_errors`:回调抛出的异常。 单个回调抛出异常不会阻止同一批次的其他任务执行。`AdvanceTo()` 不允许在定时器回调中重入;重入错误会作为回调异常收集,避免死锁。 ## 自定义时间轮配置 默认配置为 `{256, 64, 64, 64}`,顺序从最细层到最粗层。每个上层槽位宽度等于下层完整覆盖范围。 ```cpp timewheel::SchedulerOptions options; options.tick_ms = 10; options.start_time_ms = first_timestamp; options.wheel_sizes = {256, 64, 64, 64}; options.fast_forward_threshold_ticks = 4096; timewheel::Scheduler scheduler(options); ``` 超过最粗层覆盖范围的任务自动进入 overflow 集合;不需要为长时间任务手动增加时间轮层级。 快进时 Scheduler 按成本选择策略:跳过空档的成本正比于 gap,重建槽位的成本正比于活跃任务数。只有当 gap 超过 `fast_forward_threshold_ticks` 且大于活跃任务数时才重建,因此大量长定时器配合大步长推进不会退化为每步全量重建。 ## 回测同步实例 `HandledInstance` 拥有一个 Scheduler,由调用线程显式驱动,适合行情回放和固定步长仿真。它与 RealtimeInstance 使用相同的 `TimerErrorHandler` 上报回调异常,两种封装可以互换而不改动策略代码: ```cpp #include "timewheel/timewheel.h" timewheel::HandledInstance instance( /* tick_ms */ 10, /* start_time_ms */ replay_events.front().timestamp_ms, [](std::exception_ptr error) { // 处理回调抛出的异常;在调用线程中同步触发。 }); instance.scheduler().CreateTimerAfter(500, [] { /* ... */ }); // 按事件时间戳推进: for (const auto& event : replay_events) { instance.AdvanceTo(event.timestamp_ms); ProcessMarketEvent(event); } // 或者按相对步长仿真: instance.Tick(); // 前进一个时间轮 tick(tick_ms) instance.Tick(100); // 前进 100ms ``` `Tick(step_ms)` 基于 `Now()` 相对推进,负步长抛出 `std::invalid_argument`,时间溢出抛出 `std::overflow_error`。错误处理器抛出的异常会被忽略,避免污染回放主流程。也可以直接用 `instance.scheduler()` 访问全部 Scheduler API。 ## 实时模式 ### RealtimeInstance 便利封装 ```cpp #include #include #include #include "timewheel/timewheel.h" int main() { timewheel::RealtimeInstance instance(50); std::atomic fired{false}; instance.scheduler().CreateTimerAfter(100, [&] { fired.store(true); }); while (!fired.load()) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); } instance.Stop(); } ``` 构造函数接受可选的 `TimerErrorHandler`,并透传 `Start()`、`Stop()`、`RequestStop()`、`running()`。 ### 自行组合 RealtimeDriver ```cpp const auto start = timewheel::RealtimeDriver::NowMs(); timewheel::Scheduler scheduler(50, start); timewheel::RealtimeDriver driver( scheduler, [](std::exception_ptr error) { // 处理实时线程中的定时器异常。 }); driver.Start(); // ... driver.Stop(); ``` RealtimeDriver 使用 `steady_clock`,不同实例互不影响。工作线程在有任务时直接睡眠到最早 deadline,无任务时等待调度状态变化,不做周期轮询。实时驱动运行期间,`CreateTimerAfter()` 和 `CreateTimerEvery()` 的首次 deadline 以调用时的单调时钟为基准;新建、取消最早任务会立即唤醒工作线程。不要在定时器回调内部销毁 RealtimeDriver。回调内需要停止时调用 `RequestStop()`,随后由拥有者线程调用 `Stop()` 完成 join。 ## 线程和生命周期约束 - 同一个 Scheduler 同时只能有一个时间驱动线程调用 `AdvanceTo()`。 - `AdvanceTo()` 不可重入,但不同线程的推进调用会被串行化。 - 创建和取消任务是线程安全的。 - 为了让行情回放完全确定,推荐从回放线程操作任务,或在外部通过命令队列串行化跨线程请求。 - Scheduler 必须比引用它的 RealtimeDriver 活得更久;`RealtimeInstance` 已按正确的成员析构顺序封装这个约束。 - 同一个 Scheduler 最多关联一个 RealtimeDriver;第二个驱动的 `Start()` 会抛出 `std::logic_error`。 ## 从 2.x 迁移到 3.0 3.0 不保留 2.x 的兼容别名和转发头: | 2.x | 3.0 | | --- | --- | | `timewheel::Instance` / `getScheduler()` | `timewheel::RealtimeInstance` / `scheduler()` | | `timewheel/instance.h` | `timewheel/realtime_instance.h` | | `timewheel/details/realtime_driver.h` + `_impl.h` | `timewheel/realtime_driver.h`(单文件) | | `RealtimeDriver(scheduler, poll_interval_ms, handler)` | `RealtimeDriver(scheduler, handler)`;驱动不再轮询 | ## 从 1.x 迁移 2.0 是一次破坏性升级: | 1.x | 2.0 | | --- | --- | | `Scheduler(tick, Clock*)` | `Scheduler(tick, start_time_ms)` | | `Clock::NotifyTick(time)` | `Scheduler::AdvanceTo(time)` | | `Scheduler::Start/Stop()` | 模拟模式无需启动;实时模式使用 `RealtimeDriver` | | `AppendTimeWheel(scales, unit)` | 通过 `SchedulerOptions::wheel_sizes` 配置 | | 全局 `SystemClock` 工作线程 | 每个 RealtimeDriver 拥有独立工作线程 | | `uint32_t` Timer ID | `uint64_t TimerId` | | `void CancelTimer(id)` | `bool CancelTimer(id)` | 行情回放迁移示例: ```cpp // 1.x // MockClock clock; // Scheduler scheduler(50, &clock); // clock.Advance(100); // 2.0 timewheel::Scheduler scheduler(50, initial_time_ms); scheduler.AdvanceTo(initial_time_ms + 100); ``` ## 构建和测试 需要 CMake 3.24 或更高版本。 ```bash ./conan/conan_download.sh cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DTIMEWHEEL_BUILD_TESTS=ON cmake --build build --parallel ctest --test-dir build --output-on-failure ``` `conan_download.sh` 会将 GoogleTest 1.14.0 安装到 `deps/`,并保留在本机 Conan cache 中供后续安装复用。 GoogleTest 仅作为测试依赖,不会传递给使用 header-only timewheel 包的项目。 作为 CMake 子项目使用: ```cmake add_subdirectory(path/to/timewheel) target_link_libraries(your_target PRIVATE timewheel::timewheel) ``` 安装并使用: ```bash cmake -S . -B build -DTIMEWHEEL_BUILD_TESTS=OFF cmake --build build cmake --install build --prefix /your/install/prefix ``` ```cmake find_package(timewheel 2 CONFIG REQUIRED) target_link_libraries(your_target PRIVATE timewheel::timewheel) ``` 创建 Conan 2 包: ```bash conan create conan --build=missing ``` ## License MIT License,详见 [LICENSE](LICENSE)。