一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

第一章:定时器的本质与核心指标

时间:2026-06-03 08:50:01 编辑:袖梨 来源:一聚教程网

在构建网络服务器、游戏后端及分布式系统时,“时间”成为不可忽视的核心维度。本章从超时处理、心跳检测等现实场景出发,深入剖析定时器的本质及其关键衡量指标。

第1章:定时器的本质与核心指标


1.1 定时器解决了什么问题?

1.1.1 问题背景:时间是无处不在的资源

在网络服务器、游戏后端、分布式系统中,"时间"是一个核心维度。以下场景揭示了其重要性:

第1章:定时器的本质与核心指标

场景问题描述没有定时器会怎样?
TCP连接超时客户端建立连接后长时间不发送数据连接永久占用资源,服务器崩溃
心跳检测检测客户端是否存活无法感知客户端宕机,资源泄漏
延迟任务30分钟后发送验证码需要轮询数据库,效率极低
周期性调度每秒统计QPS并上报需要独立线程+sleep,精度差
缓存过期Redis键的TTL机制内存无限增长
技能冷却游戏中技能释放后的冷却时间玩家可无限释放技能

定时器的核心功能在于赋予程序在预定时间执行特定代码的能力。

1.1.2 四大典型应用场景

场景一:超时处理(Timeout Handling)

定时器最基础的应用是网络编程中的超时设置,每个连接都需要为此配置超时时间。

┌─────────────────────────────────────────────────────────────┐
│                    TCP连接超时处理流程                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   客户端连接 ──► 创建连接对象 ──► 添加超时定时器(30s)          │
│                                      │                      │
│                                      ▼                      │
│                              ┌──────────────┐              │
│                              │  等待数据到达  │              │
│                              └──────┬───────┘              │
│                                     │                      │
│                    ┌────────────────┼────────────────┐     │
│                    ▼                ▼                ▼     │
│              [数据到达]        [30s超时]         [连接关闭]
│                  │                │                │      │
│                  ▼                ▼                ▼      │
│           删除定时器          关闭连接          删除定时器   │
│           处理数据            记录日志                       │
│                                                             │
└─────────────────────────────────────────────────────────────┘

代码示例:连接超时管理

#include 
#include 
#include class ConnectionManager {
public:
    using ConnectionId = uint64_t;
    using TimerId = uint64_t;
    using TimePoint = std::chrono::steady_clock::time_point;
    
    // 添加连接,设置30秒超时
    void AddConnection(ConnectionId conn_id, int fd) {
        // 存储连接信息
        connections_[conn_id] = {fd, /*last_active=*/Now()};
        
        // 添加超时定时器
        TimerId timer_id = timer_manager_.AddTimer(
            std::chrono::seconds(30),
            [this, conn_id]() {
                OnConnectionTimeout(conn_id);
            }
        );
        
        // 记录定时器ID,方便后续取消
        connection_timers_[conn_id] = timer_id;
    }
    
    // 连接有活动,重置超时时间(心跳续期)
    void RefreshConnection(ConnectionId conn_id) {
        auto it = connections_.find(conn_id);
        if (it != connections_.end()) {
            it->second.last_active = Now();
            
            // 删除旧定时器,添加新定时器
            timer_manager_.CancelTimer(connection_timers_[conn_id]);
            TimerId new_timer = timer_manager_.AddTimer(
                std::chrono::seconds(30),
                [this, conn_id]() { OnConnectionTimeout(conn_id); }
            );
            connection_timers_[conn_id] = new_timer;
        }
    }
    
    // 超时回调
    void OnConnectionTimeout(ConnectionId conn_id) {
        printf("Connection %lu timeout, closing...n", conn_id);
        CloseConnection(conn_id);
    }
    
private:
    TimePoint Now() { return std::chrono::steady_clock::now(); }
    
    struct Connection {
        int fd;
        TimePoint last_active;
    };
    
    std::unordered_map connections_;
    std::unordered_map connection_timers_;
    TimerManager timer_manager_;  // 假设已实现
};
场景二:心跳检测(Heartbeat Detection)

在长连接场景(如WebSocket、RPC框架、游戏服务器)中,心跳是检测对端存活的核心机制。

┌────────────────────────────────────────────────────────────────┐
│                       心跳检测机制                              │
├────────────────────────────────────────────────────────────────┤
│                                                                │
│  服务端                                    客户端              │
│    │                                         │                │
│    │◄─────── 心跳请求 ──────────────────────│  每5秒          │
│    │                                        │                │
│    │─────── 心跳响应 ──────────────────────►│                │
│    │                                        │                │
│    │  [更新最后活跃时间]                      │                │
│    │                                        │                │
│    │◄─────── 心跳请求 ──────────────────────│                │
│    │─────── 心跳响应 ──────────────────────►│                │
│    │                                        │                │
│    │  [超过15秒无心跳]                        │                │
│    │                                        │                │
│    │   判定客户端离线                        │                │
│    │   主动关闭连接                         │                │
│    │   释放相关资源                         │                │
│                                                                │
└────────────────────────────────────────────────────────────────┘

心跳检测的关键参数:

参数典型值说明
心跳间隔5-10秒客户端发送心跳的频率
超时阈值15-30秒服务端判定离线的时间
心跳超时次数2-3次连续未响应次数才判定离线
场景三:延迟任务(Delayed Task)

延迟任务是定时器的另一项重要应用,常见于订单超时自动取消、验证码过期、定时推送消息及分布式任务调度等场景。

  1. 订单超时自动取消(30分钟)
  2. 验证码过期(5分钟)
  3. 定时推送消息
  4. 分布式任务调度
┌─────────────────────────────────────────────────────────────┐
│                     延迟任务执行流程                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   用户下单 ──► 创建订单 ──► 添加延迟任务(30分钟后执行)         │
│                                │                            │
│                                ▼                            │
│                        ┌──────────────┐                    │
│                        │  等待30分钟   │                    │
│                        └──────┬───────┘                    │
│                               │                            │
│              ┌────────────────┴────────────────┐           │
│              ▼                                 ▼           │
│        [订单已支付]                       [订单未支付]
│            │                                 │            │
│            ▼                                 ▼            │
│      取消延迟任务                        执行取消订单        │
│      订单完成                            释放库存           │
│                                         通知用户           │
│                                                             │
└─────────────────────────────────────────────────────────────┘

代码示例:订单超时处理

#include 
#include 
#include class OrderService {
public:
    using OrderId = std::string;
    
    // 创建订单,设置30分钟超时
    void CreateOrder(const OrderId& order_id, int amount) {
        // 1. 创建订单记录
        orders_[order_id] = {amount, OrderStatus::PENDING, Now()};
        
        // 2. 添加延迟任务:30分钟后检查订单状态
        timer_manager_.AddTimer(
            std::chrono::minutes(30),
            [this, order_id]() {
                CheckOrderTimeout(order_id);
            }
        );
        
        printf("Order %s created, will timeout in 30 minutesn", 
               order_id.c_str());
    }
    
    // 支付订单
    void PayOrder(const OrderId& order_id) {
        auto it = orders_.find(order_id);
        if (it != orders_.end() && it->second.status == OrderStatus::PENDING) {
            it->second.status = OrderStatus::PAID;
            printf("Order %s paidn", order_id.c_str());
            // 注意:定时器仍然会触发,但检查时会发现已支付
        }
    }
    
private:
    void CheckOrderTimeout(const OrderId& order_id) {
        auto it = orders_.this, order_id]() {
                CheckOrderTimeout(order_id);
            }
        );
        
        printf("Order %s created, will timeout in 30 minutesn", 
               order_id.c_str());
    }
    
    // 支付订单
    void PayOrder(const OrderId& order_id) {
        auto it = orders_.find(order_id);
        if (it != orders_.end() && it->second.status == OrderStatus::PENDING) {
            it->second.status = OrderStatus::PAID;
            printf("Order %s paidn", order_id.c_str());
            // 注意:定时器仍然会触发,但检查时会发现已支付
        }
    }
    
private:
    void CheckOrderTimeout(const OrderId& order_id) {
        auto it = orders_.find(order_id);
        if (it != orders_.Cod
if (it != orders_.end() && it->second.status == OrderStatus::PENDING) {
            // 订单未支付,执行超时逻辑
            it->second.status = OrderStatus::TIMEOUT;
            printf("Order %s timeout, auto cancelledn", order_id.c_str());
            
            // 释放库存、通知用户等...
            ReleaseInventory(it->second.amount);
        }
    }
    
    void ReleaseInventory(int amount) {
        // 释放库存逻辑...
    }
    
    enum class OrderStatus { PENDING, PAID, TIMEOUT };
    
    struct Order {
        int amount;
        OrderStatus status;
        std::chrono::steady_clock::time_point create_time;
    };
    
    std::unordered_map orders_;
    TimerManager timer_manager_;
};
场景四:周期性调度(Periodic Scheduling)

周期性任务需要反复执行,包括监控指标采集、数据库连接池健康检查、日志文件轮转和缓存预热等。

  1. 监控指标采集与上报(每秒)
  2. 数据库连接池健康检查(每10秒)
  3. 日志文件轮转(每天)
  4. 缓存预热(每小时)
┌─────────────────────────────────────────────────────────────┐
│                    周期性任务执行示意                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   时间轴 ──────────────────────────────────────────────►     │
│                                                             │
│   任务A(每1秒):  ●───●───●───●───●───●───●───●───►          │
│                  │   │   │   │   │   │   │   │              │
│                  ▼   ▼   ▼   ▼   ▼   ▼   ▼   ▼              │
│                 采集 采集 采集 采集 采集 采集 采集 采集        │
│                                                             │
│   任务B(每5秒):  ●─────────●─────────●─────────►            │
│                  │         │         │                      │
│                  ▼         ▼         ▼                      │
│                 检查      检查      检查                     │
│                                                             │
│   任务C(每10秒): ●───────────────●───────────────►          │
│                  │               │                          │
│                  ▼               ▼                          │
│                 清理            清理                         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

代码示例:周期性任务管理

class MetricsCollector {
public:
    MetricsCollector() : qps_(0), latency_sum_(0), request_count_(0) {}
    
    void Start() {
        // 每秒采集一次QPS
        timer_manager_.AddPeriodicTimer(
            std::chrono::seconds(1),
            [this]() { CollectAndReport(); }
        );
        
        // 每10秒检查一次健康状态
        timer_manager_.AddPeriodicTimer(
            std::chrono::seconds(10),
            [this]() { HealthCheck(); }
        );
        
        printf("Metrics collector startedn");
    }
    
    void RecordRequest(int latency_ms) {
        qps_++;
        latency_sum_ += latency_ms;
        request_count_++;
    }
    
private:
    void CollectAndReport() {
        // 计算并上报QPS和平均延迟
        int current_qps = qps_.exchange(0);  // 原子操作并重置
        
        double avg_latency = 0.0;
        if (request_count_ > 0) {
            avg_latency = static_cast<double>(latency_sum_) / request_count_;
            latency_sum_ = 0;
            request_count_ = 0;
        }
        
        printf("[Metrics] QPS: %d, AvgLatency: %.2fmsn", 
               current_qps, avg_latency);
        
        // 上报到监控系统...
    }
    
    void HealthCheck() {
        printf("[Health] System running normallyn");
        // 检查数据库连接、内存使用等...
    }
    
    std::atomic<int> qps_;
    std::atomic<long long> latency_sum_;
    std::atomic<int> request_count_;
    TimerManager timer_manager_;
};

1.2 定时器的两大核心操作

通过分析上述场景,可以抽象出定时器的核心接口。无论底层实现是最小堆、时间轮还是红黑树,对外暴露的接口都是一致的。

1.2.1 定时器的抽象接口

/**
 * 定时器管理器抽象接口
 * 
 * 设计原则:
 * 1. 添加定时器返回唯一ID,用于后续取消
 * 2. 支持单次定时器和周期定时器
 * 3. Tick()由外部事件循环驱动,不创建独立线程
 */
class TimerManager {
public:
    using TimerId = uint64_t;
    using Duration = std::chrono::milliseconds;
    using Callback = std::function<void()>;
    
    virtual ~TimerManager() = default;
    
    /**
     * 添加一个单次定时器
     * @param delay 延迟时间
     * @param callback 到期回调
     * @return 定时器ID,用于取消
     */
    virtual TimerId AddTimer(Duration delay, Callback callback) = 0;
    
    /**
     * 添加一个周期定时器
     * @param interval 周期间隔
     * @param callback 每次到期回调
     * @return 定时器ID,用于取消
     */
    virtual TimerId AddPeriodicTimer(Duration interval, Callback callback) = 0;
    
    /**
     * 取消定时器
     * @param id 要取消的定时器ID
     * @return 是否成功取消(定时器可能已执行)
     */
    virtual bool CancelTimer(TimerId id) = 0;
    
    /**
     * 驱动定时器前进
     * @param now 当前时间点
     * @return 下一个最近定时器的到期时间(用于设置epoll_wait超时)
     */
    virtual std::optional Tick(std::chrono::steady_clock::time_point now) = 0;
    
    /**
     * 获取最近定时器的到期时间
     * @return 如果有待执行定时器,返回到期时间;否则返回空
     */
    virtual std::optional 
    GetNextExpiry() const = 0;
};

1.2.2 AddTimer:添加定时器

AddTimer 是定时器模块最频繁的操作,其性能直接影响系统吞吐量。

操作流程:

┌─────────────────────────────────────────────────────────────┐
AddTimer 执行流程                         
├─────────────────────────────────────────────────────────────┤

输入: delay=5000ms, callback=OnTimeout                      

Step 1: 计算到期时间                                        
expiry = now + delay = 14:30:05.000                

Step 2: 分配定时器ID                                        
timer_id = atomic_fetch_add(&next_id_, 1) = 42     

Step 3: 创建定时器对象                                       
Timer { id: 42, expiry: 14:30:05, cb: OnTimeout }  

Step 4: 插入数据结构                                        
┌─────────────────────────────────────┐            
  最小堆: O(log n) 上滤操作                       
  时间轮: O(1) 计算槽位插入                       
  红黑树: O(log n) 按key插入                      
└─────────────────────────────────────┘            

Step 5: 返回定时器ID                                        
return 42                                          

└─────────────────────────────────────────────────────────────┘

不同实现的AddTimer复杂度:

数据结构时间复杂度空间复杂度说明
最小堆O(log n)O(n)需要上滤操作
时间轮O(1)O(n + m)m为槽数量,直接计算槽位
红黑树O(log n)O(n)按到期时间作为key
跳表O(log n)O(n)期望复杂度

1.2.3 Tick:驱动定时器

Tick 是定时器的核心驱动机制,由外部事件循环(如 epoll_wait)负责调用。

操作流程:

┌─────────────────────────────────────────────────────────────┐
│                      Tick 执行流程                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  输入: now = 14:30:05.100
│                                                             │
│  Step 1: 检查是否有到期定时器                                 │
│          while (top.expiry <= now)                          │
│                                                             │
│  Step 2: 取出到期定时器                                       │
│          timer = heap.pop()  // 或从时间轮当前槽取出          │
│                                                             │
│  Step 3: 检查是否已取消(惰性删除)                           │
│          if (canceled_[timer.id]) continue;                 │
│                                                             │
│  Step 4: 执行回调                                            │
│          timer.callback()                                   │
│          ├── 可能触发新定时器添加                             │
│          ├── 可能取消其他定时器                               │
│          └── 可能阻塞(危险!)                               │
│                                                             │
│  Step 5: 如果是周期定时器,重新添加                           │
│          if (timer.is_periodic) {                           │
│              timer.expiry += timer.interval;                │
│              heap.push(timer);                              │
│          }                                                  │
│                                                             │
│  Step 6: 返回下一个到期时间                                   │
│          return heap.top().expiry - now;                    │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Tick与事件循环的集成:

#include 
#include class EventLoop {
public:
    EventLoop() {
        epoll_fd_ = epoll_create1(0);
    }
    
    void Run() {
        while (running_) {
            // 计算epoll_wait的超时时间
            int timeout_ms = -1;  // 默认阻塞
            auto next_expiry = timer_manager_.GetNextExpiry();
            if (next_expiry.has_value()) {
                auto now = std::chrono::steady_clock::now();
                auto wait_duration = 
                    std::chrono::duration_cast(
                        *next_expiry - now
                    );
                timeout_ms = std::max(0, static_cast<int>(wait_duration.count()));
            }
            
            // 等待IO事件或定时器到期
            struct epoll_event events[128];
            int n = epoll_wait(epoll_fd_, events, 128, timeout_ms);
            
            // 处理IO事件
            for (int i = 0; i < n; ++i) {
                HandleEvent(events[i]);
            }
            
            // 驱动定时器
            timer_manager_.Tick(std::chrono::steady_clock::now());
        }
    }
    
private:
    int epoll_fd_;
    bool running_ = true;
    TimerManager timer_manager_;
};

1.2.4 两大操作的协作关系

┌─────────────────────────────────────────────────────────────────────┐
│                    AddTimer 与 Tick 的协作                          │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   ┌──────────────┐                              ┌──────────────┐    │
│   │  业务线程     │                              │  IO线程       │    │
│   └──────┬───────┘                              └──────┬───────┘    │
│          │                                             │           │
│          │ AddTimer(5s, cb1)                           │           │
│          │─────────────────────────────────────────────►           │
│          │                                             │           │
│          │                    ┌────────────────────┐   │           │
│          │                    │  TimerHeap:        │   │           │
│          │                    │  [14:30:05, cb1]   │   │           │
│          │                    │  [14:30:10, cb2]   │   │           │
│          │                    └────────────────────┘   │           │
│          │                                             │           │
│          │                                             │ epoll_wait│
│          │                                             │ timeout=5s│
│          │                                             │           │
│          │                                             │ [5秒后]   │
│          │                                             │           │
│          │                                             │ Tick()    │
│          │                                             │ cb1()     │
│          │                                             │           │
│          │ AddTimer(3s, cb3)                           │           │
│          │─────────────────────────────────────────────►           │
│          │                                             │           │
│          │                    ┌────────────────────┐   │           │
│          │                    │  TimerHeap:        │   │           │
│          │                    │  [14:30:08, cb3]   │   │           │
│          │                    │  [14:30:10, cb2]   │   │           │
│          │                    └────────────────────┘   │           │
│          │                                             │           │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

1.3 衡量定时器模块的关键指标

在设计或选择定时器实现时,需要从以下四个维度进行评估。

1.3.1 精度(Precision)

定义:定时器实际执行时间与期望执行时间的偏差。

影响因素:

  1. 时钟源精度

    1. clock_gettime(CLOCK_MONOTONIC) 可达纳秒级
    2. std::chrono::steady_clock 通常封装了上述系统调用
    3. gettimeofday() 已废弃,不推荐使用
  2. 驱动粒度

    1. epoll_wait 的超时参数是毫秒级
    2. 时间轮的 tick_interval 决定了最小精度
  3. 系统调度延迟

    1. 高负载系统可能导致回调延迟执行
    2. 实时内核(PREEMPT_RT)可改善

精度测试代码:

#include 
#include 
#include void TestTimerPrecision() {
    const int test_count = 1000;
    const int target_delay_ms = 10;
    
    std::vector<double> errors;
    errors.reserve(test_count);
    
    for (int i = 0; i < test_count; ++i) {
        auto start = std::chrono::steady_clock::now();
        
        // 模拟定时器等待
        std::this_thread::sleep_for(std::chrono::milliseconds(target_delay_ms));
        
        auto end = std::chrono::steady_clock::now();
        auto actual_ms = std::chrono::duration_cast(
            end - start
        ).count() / 1000.0;
        
        errors.push_back(actual_ms - target_delay_ms);
    }
    
    // 统计分析
    double sum = 0, max_err = 0, min_err = 1e9;
    for (double e : errors) {
        sum += e;
        max_err = std::max(max_err, e);
        min_err = std::min(min_err, e);
    }
    double avg_err = sum / test_count;
    
    printf("Timer Precision Test (target: %dms, samples: %d)n", 
           target_delay_ms, test_count);
    printf("  Average error: %.3f msn", avg_err);
    printf("  Max error:     %.3f msn", max_err);
    printf("  Min error:     %.3f msn", min_err);
    printf("  Jitter range:  %.3f msn", max_err - min_err);
}

典型输出:

Timer Precision Test (target: 10ms, samples: 1000)
  Average error: 0.042 ms
  Max error:     1.234 ms
  Min error:     -0.012 ms
  Jitter range:  1.246 ms

1.3.2 吞吐量(Throughput)

定义:单位时间内能处理的定时器增删操作次数。

测试场景:

┌─────────────────────────────────────────────────────────────┐
│                    吞吐量测试场景                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  场景A:高频添加/取消                                        │
│  ─────────────────────                                      │
│  for (int i = 0; i < 1000000; ++i) {                        │id = AddTimer(random_delay, cb);                       │
│      CancelTimer(id);                                       │
│  }                                                          │
│                                                             │
│  场景B:大量定时器到期                                       │
│  ─────────────────────                                      │
│  for (int i = 0; i < 100000; ++i) {                         │
│      AddTimer(1ms, cb);  // 同时添加大量1ms后到期的定时器    │
│  }                                                          │
│  Tick();  // 触发执行                                       │
│                                                             │
│  场景C:周期定时器持续运行                                   │
│  ─────────────────────                                      │
│  for (int i = 0; i < 10000; ++i) {                          │
│      AddPeriodicTimer(10ms, cb);                            │
│  }                                                          │
│  for (int i = 0; i < 10000; ++i) {                          │
│      Tick();  // 持续驱动10秒                                │
│  }                                                          │
│                                                             │
└─────────────────────────────────────────────────────────────┘

吞吐量测试代码:

#include 
#include void TestTimerThroughput(TimerManager& timer_manager) {
    const int add_cancel_count = 1000000;
    const int expire_count = 100000;
    
    // 测试1:添加+取消吞吐量
    {
        auto start = std::chrono::steady_clock::now();
        
        for (int i = 0; i < add_cancel_count; ++i) {
            auto id = timer_manager.AddTimer(
                std::chrono::milliseconds(1000),
                [](){}
            );
            timer_manager.CancelTimer(id);
        }
        
        auto end = std::chrono::steady_clock::now();
        auto ms = std::chrono::duration_cast(
            end - start
        ).count();
        
        double ops_per_sec = (add_cancel_count * 2.0) / (ms / 1000.0);
        printf("Add/Cancel Throughput: %.0f ops/secn", ops_per_sec);
    }
    
    // 测试2:批量到期吞吐量
    {
        auto start = std::chrono::steady_clock::now();
        
        // 添加大量即将到期的定时器
        for (int i = 0; i < expire_count; ++i) {
            timer_manager.AddTimer(
                std::chrono::milliseconds(1),
                [](){}
            );
        }
        
        // 驱动执行
        auto now = std::chrono::steady_clock::now() + 
                   std::chrono::milliseconds(2);
        timer_manager.Tick(now);
        
        auto end = std::chrono::steady_clock::now();
        auto ms = std::chrono::duration_cast(
            end - start
        ).count();
        
        double expiries_per_sec = expire_count / (ms / 1000.0);
        printf("Expiry Throughput: %.0f timers/secn", expiries_per_sec);
    }
}

不同实现的吞吐量对比:

实现Add/Cancel (ops/sec)Expiry (timers/sec)内存/定时器
最小堆~200万~50万48 bytes
时间轮~500万~100万56 bytes
红黑树~150万~40万64 bytes

1.3.3 内存开销(Memory Overhead)

定义:单个定时器对象占用的内存大小。

内存布局分析:

// 典型Timer结构体
struct Timer {
    TimerId id;              // 8 bytes
    TimePoint expiry;        // 8 bytes (64-bit timestamp)
    Duration interval;       // 8 bytes (周期定时器用)
    Callback callback;       // 32-48 bytes (std::function)
    bool is_periodic;        // 1 byte + padding
    // 额外字段...
    // 总计: ~64-80 bytes
};// 时间轮额外开销
struct TimingWheel {
    std::vector> slots;  // 槽数组
    size_t current_slot;                   // 当前指针
    size_t slot_count;                     // 槽数量
    Duration tick_interval;                // 精度
    // 每个槽的链表头节点: 16-24 bytes
    // 总开销: slot_count * 24 bytes
};

内存优化策略:

  1. 使用函数指针代替 std::function

    // 原始:std::function 开销大
    using Callback = std::function<void()>;// 优化:函数指针 + void* context
    using Callback = void(*)(void* ctx);
    void* callback_context;
    
  2. 内存池分配

    class TimerPool {
    public:
        Timer* Allocate() {
            if (free_list_.empty()) {
                return new Timer;
            }
            Timer* t = free_list_.back();
            free_list_.pop_back();
            return t;
        }
        
        void Deallocate(Timer* t) {
            free_list_.push_back(t);
        }
    private:
        std::vector free_list_;
    };
    
  3. 紧凑存储

    // 将周期定时器和单次定时器分开存储
    struct PeriodicTimer {
        TimerId id;
        TimePoint next_expiry;
        Duration interval;
        Callback callback;
    };struct OneShotTimer {
        TimerId id;
        TimePoint expiry;
        Callback callback;
        // 省略 interval 字段
    };
    

1.3.4 并发安全性(Thread Safety)

定义:定时器模块在多线程环境下正确工作的能力。

三种并发模型:

┌─────────────────────────────────────────────────────────────────────┐
│                    定时器并发模型对比                                │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  模型1:全局锁(简单但低效)                                         │
│  ─────────────────────────                                          │
│  ┌──────────────────────────────────────────────┐                  │
│  │  std::mutex lock_;                           │                  │
│  │                                              │                  │
│  │  TimerId AddTimer(...) {                    │                  │
│  │      std::lock_guard g(lock_);  │                  │
│  │      return DoAddTimer(...);                │                  │
│  │  }                                          │                  │
│  └──────────────────────────────────────────────┘                  │
│  优点:实现简单                                                    │
│  缺点:高并发下锁竞争严重,性能下降明显                              │
│                                                                     │
│  模型2:线程私有定时器(推荐)                                       │
│  ─────────────────────────────                                     │
│  ┌──────────────────────────────────────────────┐                  │
│  │  每个IO线程独立的TimerManager                 │                  │
│  │                                              │                  │
│  │  Thread1: TimerManager1                      │                  │
│  │  Thread2: TimerManager2                      │                  │
│  │  Thread3: TimerManager3                      │                  │
│  └──────────────────────────────────────────────┘                  │
│  优点:无锁竞争,性能最优                                          │
│  缺点:定时器不能跨线程操作                                        │
│                                                                     │
│  模型3:无锁队列转发                                                │
│  ─────────────────────────────                                     │
│  ┌──────────────────────────────────────────────┐                  │
│  │  业务线程 ──► 无锁队列 ──► 定时器线程          │                  │
│  │                                              │                  │
│  │  AddTimer() {                               │                  │
│  │      queue_.push(TimerCmd{ADD, ...});       │                  │
│  │      eventfd_notify();                      │                  │
│  │  }                                          │                  │
│  └──────────────────────────────────────────────┘                  │
│  优点:支持跨线程操作,性能较好                                     │
│  缺点:实现复杂,需要eventfd配合                                    │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

无锁队列实现示例:

#include 
#include // 简单的MPSC(多生产者单消费者)无锁队列
template<typename T>
class MPSCQueue {
public:
    struct Node {
        T data;
        std::atomic next{nullptr};
    };
    
    MPSCQueue() {
        Node* stub = new Node{};
        head_.store(stub);
        tail_ = stub;
    }
    
    // 生产者调用(多线程安全)
    void Push(T&& data) {
        Node* node = new Node{std::move(data), nullptr};
        Node* prev = head_.exchange(node);
        prev->next.store(node);
    }
    
    // 消费者调用(单线程)
    bool Pop(T& out) {
        Node* tail = tail_;
        Node* next = tail->next.load();
        
        if (next == nullptr) {
            return false;
        }
        
        out = std::move(next->data);
        tail_ = next;
        delete tail;
        return true;
    }
    
private:
    std::atomic head_;
    Node* tail_;
};// 定时器命令
struct TimerCommand {
    enum Type { ADD, CANCEL } type;
    TimerManager::TimerId id;
    std::chrono::milliseconds delay;
    std::function<void()> callback;
};// 线程安全的定时器管理器
class ThreadSafeTimerManager {
public:
    TimerManager::TimerId AddTimer(
        std::chrono::milliseconds delay,
        std::function<void()> callback
    ) {
        TimerManager::TimerId id = next_id_.fetch_add(1);
        
        command_queue_.Push(TimerCommand{
            TimerCommand::ADD,
            id,
            delay,
            std::move(callback)
        });
        
        // 通知定时器线程有新命令
        NotifyTimerThread();
        
        return id;
    }
    
    // 定时器线程调用
    void ProcessCommands() {
        TimerCommand cmd;
        while (command_queue_.Pop(cmd)) {
            if (cmd.type == TimerCommand::ADD) {
                timer_manager_.AddTimer(cmd.delay, cmd.callback);
            } else {
                timer_manager_.CancelTimer(cmd.id);
            }
        }
    }
    
private:
    void NotifyTimerThread() {
        // 使用eventfd通知...
    }
    
    std::atomic next_id_{0};
    MPSCQueue command_queue_;
    TimerManager timer_manager_;  // 非线程安全
};

1.4 本章小结

核心要点回顾

┌─────────────────────────────────────────────────────────────────────┐
│                      第一章 核心要点                                 │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  1. 定时器的本质                                                    │
│     ──────────────                                                 │
│     "在未来的某个时间点,执行某段代码"                               │
│     四大场景:超时处理、心跳检测、延迟任务、周期性调度                │
│                                                                     │
│  2. 两大核心操作                                                    │
│     ──────────────                                                 │
│     AddTimer:添加定时器,返回ID用于取消                            │
│     Tick:驱动定时器前进,执行到期回调                              │
│                                                                     │
│  3. 四大关键指标                                                    │
│     ──────────────                                                 │
│     ┌────────────┬────────────┬────────────────────┐               │
│     │   指标     │   关注点   │     影响因素       │               │
│     ├────────────┼────────────┼────────────────────┤               │
│     │   精度     │  时间偏差  │ 时钟源、驱动粒度   │               │
│     │  吞吐量    │  操作速率  │ 数据结构、算法     │               │
│     │ 内存开销   │  单定时器  │ 结构设计、内存池   │               │
│     │ 并发安全   │  多线程    │ 锁策略、无锁设计   │               │
│     └────────────┴────────────┴────────────────────┘               │
│                                                                     │
│  4. 设计选型指南                                                    │
│     ──────────────                                                 │
│     • 少量定时器(<1000):最小堆,实现简单                        │
│     • 大量定时器(>10000):时间轮,O(1)操作                       │
│     • 需要有序遍历:红黑树                                         │
│     • 高并发场景:线程私有或无锁队列                                │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

思考题

  1. 场景分析:假设你要实现一个即时通讯服务器,每个用户连接需要设置心跳超时(30秒)和消息发送超时(5秒)。请分析:

    1. 需要多少种定时器?
    2. 每个连接至少需要几个定时器实例?
    3. 如果有100万在线用户,定时器总数是多少?
  2. 性能估算:如果一个定时器操作(AddTimer + CancelTimer)需要1微秒,那么:

    1. 每秒最多能处理多少次定时器操作?
    2. 如果每个请求需要2次定时器操作,QPS上限是多少?
  3. 设计权衡:为什么大多数网络库(如libevent、Redis)选择最小堆而不是时间轮?请从实现复杂度、内存局部性、精度控制三个角度分析。


综上所述,本章系统地阐述了定时器的核心概念、典型应用场景、关键操作接口与性能评测指标,为后续深入探讨其底层实现奠定了坚实基础。下一章将聚焦于操作系统提供的时间驱动API,剖析

热门栏目