土法炼钢兴趣小组的算法知识备份

定时器管理 (Timer)

目录

在网络编程中,超时处理是必不可少的:连接超时、请求超时、心跳检测等。Libevent 将定时器也抽象为一种事件,与 I/O 事件统一管理。

1. 基本用法

1.1. 创建定时器

定时器本质上就是没有 fd 的事件(fd = -1),且标志位包含 EV_TIMEOUT

// 宏定义:等价于 event_new(base, -1, 0, cb, arg)
struct event *ev = evtimer_new(base, timer_cb, arg);

1.2. 启动定时器

调用 event_add 时传入 struct timeval

struct timeval tv = {1, 0}; // 1秒
evtimer_add(ev, &tv);

2. 内部实现:最小堆

如前文所述,Libevent 使用 最小堆 (Min-Heap) 来管理所有激活的定时器。

2.1. 插入流程

  1. 用户调用 event_add(ev, &tv)
  2. Libevent 计算绝对超时时间:now + tv
  3. ev 插入最小堆。
  4. 如果新插入的事件是堆顶(最早触发),则更新 event_base 的缓存时间。

2.2. 触发流程

  1. event_base_loop 中,查看堆顶元素的时间 min_tv
  2. 计算 wait_time = min_tv - now
  3. 调用 epoll_wait(..., wait_time)
  4. epoll_wait 返回后,再次获取当前时间 now
  5. 检查堆顶元素,如果 min_tv <= now,说明超时了。
  6. 将该事件从堆中移除,加入激活队列。
  7. 重复步骤 5,直到堆顶元素未超时。

3. Common-Timeout 优化

在某些场景下(如 HTTP 服务器),成千上万个连接的超时时间都是一样的(比如都是 5 秒)。如果都塞进最小堆,插入/删除的开销是 O(log N)。

Libevent 引入了 Common-Timeout 机制,将具有相同超时时长的事件组织成一个 FIFO 队列(双向链表)。 * 插入: 直接追加到链表尾部,O(1)。 * 检查: 只需检查链表头部的事件是否超时,O(1)。

这是一种针对特定场景的极致优化。

4. 时间精度

Libevent 默认使用 gettimeofday (微秒级) 或 clock_gettime (纳秒级)。但在高负载下,频繁调用系统调用获取时间也是开销。 Libevent 会在每次 Event Loop 迭代开始时缓存当前时间,后续操作优先使用缓存时间,以减少系统调用。

5. 总结

Libevent 的定时器管理兼顾了通用性(最小堆)和特殊场景性能(Common-Timeout)。理解这一点,有助于我们在设计海量连接系统时,合理设置超时策略,避免性能瓶颈。


上一篇: 02-data/data-structures.md - 基础数据结构 下一篇: 03-events/signal.md - 信号处理

返回 Libevent 专题索引


By .