在网络编程中,超时处理是必不可少的:连接超时、请求超时、心跳检测等。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. 插入流程
- 用户调用
event_add(ev, &tv)。 - Libevent 计算绝对超时时间:
now + tv。 - 将
ev插入最小堆。 - 如果新插入的事件是堆顶(最早触发),则更新
event_base的缓存时间。
2.2. 触发流程
- 在
event_base_loop中,查看堆顶元素的时间min_tv。 - 计算
wait_time = min_tv - now。 - 调用
epoll_wait(..., wait_time)。 epoll_wait返回后,再次获取当前时间now。- 检查堆顶元素,如果
min_tv <= now,说明超时了。 - 将该事件从堆中移除,加入激活队列。
- 重复步骤 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 - 信号处理