用event_new创建一次性定时器需设fd为-1、传ev_timeout、设timeval超时值并调用event_add()启动;重复定时器须在回调末尾重新调用event_add();取消用event_del(),不可重用已del的event对象。

怎么用 event_new 创建一个一次性定时器
libevent 的定时器本质是带超时的事件,不是独立类型;必须用 event_new 绑定一个回调函数,并设置超时时间。关键点在于:不能只传 EV_TIMEOUT,还得把 fd 设为 -1(表示无文件描述符),否则 event 可能被误判为 I/O 事件而无法触发。
常见错误是传了合法 fd(比如 0)还加 EV_TIMEOUT,结果定时器根本不回调——因为 libevent 认为这是个“监听 stdin 可读 + 超时”的混合事件,而 stdin 并不满足可读条件,超时逻辑就被压制了。
- 回调函数签名必须是
void (*cb)(evutil_socket_t, short, void *),第三个参数是用户数据 - 超时时间用
struct timeval,注意tv_sec和tv_usec都为 0 表示“立即触发”,但某些老版本 libevent 在tv_sec == 0 && tv_usec == 0时行为不稳定,建议至少设tv_usec = 1 - 调用
event_add()才真正启动定时器;漏掉这步,事件永远不生效
怎么实现重复执行的定时器(如每秒一次)
libevent 没有原生的 “repeat” 标志,重复定时器靠在回调里重新调用 event_add() 实现。这不是 bug,是设计选择:避免隐式状态,把控制权完全交给用户。
容易踩的坑是忘记在回调中再次调用 event_add(),或者错误地在回调外反复调用 event_add()(导致多个 pending 事件堆积)。正确做法是在回调末尾重设超时并再加一次:
void on_timer(evutil_socket_t fd, short what, void *arg) {
printf("tick\n");
struct event *ev = (struct event *)arg;
struct timeval one_sec = {1, 0};
event_add(ev, &one_sec); // 重新加入事件循环
}
注意:不要在回调里调用 event_free(),否则下一次 event_add() 会 crash;释放必须在确定不再需要时,由外部逻辑统一处理。
event_pending() 和 event_del() 怎么安全取消定时器
想中途停掉一个还没触发的定时器?必须用 event_del()。它会从 event_base 中移除该事件,之后即使超时也不会回调。如果只是想确认是否还在 pending 状态,用 event_pending() 检查返回值是否含 EV_TIMEOUT。
典型误操作:调用 event_del() 后又对同一 struct event * 调用 event_add() —— 这是未定义行为,多数情况下会 segfault。libevent 不允许重用已被 event_del() 的 event 对象,必须重新 event_new()。
-
event_del()是线程不安全的,若 timer 在其他线程中运行,需确保调用前已同步停止 event loop 或使用event_base_loopbreak()配合主循环检查 - 如果 timer 回调正在执行,
event_del()仍可安全调用,它只影响 future 触发,不影响当前正在跑的回调
为什么 event_base_dispatch() 不返回,以及怎么优雅退出
定时器要工作,必须让 event loop 跑起来。event_base_dispatch() 是阻塞的,直到所有事件都已删除且无 pending 事件才返回。如果你只加了一个定时器又没设循环重装,它触发一次就退出,程序直接结束。
真正实用的写法是配合 event_base_loop() 并传入 EVLOOP_NO_EXIT_ON_EMPTY,这样即使暂时没事件,loop 也不会退出,方便后续动态增删 timer。退出时推荐先 event_del() 所有 timer,再调用 event_base_loopbreak(),最后 event_base_free()。
别依赖信号或全局 flag 强行 break loop——libevent 的事件调度依赖内部状态一致性,粗暴中断可能让 pending timer 残留或内存泄漏。











