http://www.wangafu.net/~nickm/libevent-book/Ref3_eventloop.html
event loop如何工作的
开启一个循环
当你创建了一个event_base
,并且注册了一些event之后,你就想等待这些event通知事件给你。
#define EVLOOP_ONCE 0x01
#define EVLOOP_NONBLOCK 0x02
#define EVLOOP_NO_EXIT_ON_EMPTY 0x04
int event_base_loop(struct event_base *base, int flags);
一般情况下event_base_loop()
会运行一个event_base
直到没有注册事件在里面了。开启循环后,它就会不断的检测是否有事件触发。如果有,就把所有触发的事件标为活动状态,然后运行它们。
可以通过event_base_loop()
修改循环的行为,传递对应的flag进去就可以。EVLOOP_ONCE
从字面上也可以看出,等待事件触发,然后运行事件,然后退出;ONECE就表示只会执行一次。EVLOOP_NONBLOCK
很明显就是非阻塞的,它会不断检查,并不会阻塞在那里等待事件触发,如果有事件触发,就直接回调函数,没有事件触发,就继续下一次循环。
通常,当没有挂起或活动的事件时循环会立马退出。可以传入EVLOOP_NO_EXIT_ON_EMPTY
标签修改这种行为。比如,你开起了一个事件循环,想从另一个线程增加事件,所以需要保持这个循环不要退出。设置了这个标签之后,就只能调用event_base_loopbreak()
或是event_base_loopexit()
退出循环了,或是遇到了错误,也会退出循环。
event_base_loop()
有返回值,0-表示正常退出,-1-表示遇到了错误,1-表示没有挂起或是活动的事件,自动退出。
这里有一份伪代码说明一下event loop如何工作的
while (any events are registered with the loop,
or EVLOOP_NO_EXIT_ON_EMPTY was set) {
if (EVLOOP_NONBLOCK was set, or any events are already active)
If any registered events have triggered, mark them active.
else
Wait until at least one event has triggered, and mark it active.
for (p = 0; p < n_priorities; ++p) {
if (any event with priority of p is active) {
Run all active events with priority of p.
break; /* Do not run any events of a less important priority */
}
}
if (EVLOOP_ONCE was set or EVLOOP_NONBLOCK was set)
break;
}
还有一个更方便的函数
int event_base_dispatch(struct event_base *base);
这个与event_base_loop()
功能一样,只不过不用填写flag了,它自己默认了flag,行为就是会一直运行,直到没有注册事件或是调用了event_base_loopbreak()
或event_base_loopexit()
。
停止循环
上面我们提到了两个函数,可以停止一个已经开启的循环
int event_base_loopexit(struct event_base *base,
const struct timeval *tv);
int event_base_loopbreak(struct event_base *base);
event_base_loopexit()
可以传入一个超时时间,超时时间到了之后就会退出,如果传入的tv是NULL,就表示立马退出。如果这是有激活的事件列表在运行,那么就会等待所有激活的事件运行结束后退出。
event_base_loopbreak()
与event_base_loopexit(base, NULL)
的行为有一些区别, 如果有激活时间在运行,event_base_loopbreak()
会等待第一个完成后直接退出,而不是等待所有的完成后再退出。
当没有事件循环在跑时,调用这两个API行为也不一样,loopexit会在下一个event loop开始新一轮回调的时候停止(即使传入了EVLOOP_ONCE
标签),而loopbreak只会停止当前的循环,如果当前循环没有在运行,就不做任何操作。
示例:直接退出
#include <event2/event.h>
/* Here's a callback function that calls loopbreak */
void cb(int sock, short what, void *arg)
{
struct event_base *base = arg;
event_base_loopbreak(base);
}
void main_loop(struct event_base *base, evutil_socket_t watchdog_fd)
{
struct event *watchdog_event;
/* Construct a new event to trigger whenever there are any bytes to
read from a watchdog socket. When that happens, we'll call the
cb function, which will make the loop exit immediately without
running any other active events at all.
*/
watchdog_event = event_new(base, watchdog_fd, EV_READ, cb, base);
event_add(watchdog_event, NULL);
event_base_dispatch(base);
}
示例:延时退出
#include <event2/event.h>
void run_base_with_ticks(struct event_base *base)
{
struct timeval ten_sec;
ten_sec.tv_sec = 10;
ten_sec.tv_usec = 0;
/* Now we run the event_base for a series of 10-second intervals, printing
"Tick" after each. For a much better way to implement a 10-second
timer, see the section below about persistent timer events. */
while (1) {
/* This schedules an exit ten seconds from now. */
event_base_loopexit(base, &ten_sec);
event_base_dispatch(base);
puts("Tick");
}
}
有时候我们想知道调用event_base_dispatch()
或是event_base_loop()
是否正常,我们可以调用下面的接口检测一下:
int event_base_got_exit(struct event_base *base);
int event_base_got_break(struct event_base *base);
如果对应的API调用,结束了循环,就返回成功,否则,返回失败。
重新检测事件
通常libevent检测时间,然后执行所有优先级最高的事件,再检测事件。有时候我们想在当前回调结束后结束检测循环,做了一些操作后再继续。我们可以调用event_base_loopcontinue()
来实现。
int event_base_loopcontinue(struct event_base *);
如果当前没有执行事件,调用这个API没任何操作。
检测当前的时钟
有时候我们想在event回调的时候获得一个时间,我们可以调用gettimeofday()
来达到目的,但是由于系统可能把gettimeofday()
当作了系统调用,我们需要避免使用系统调用增加资源开销。在回调函数内我们可以通过API向libevent获取当前的时间。
int event_base_gettimeofday_cached(struct event_base *base,
struct timeval *tv_out);
如果event_base
当前正在执行回调,则event_base_gettimeofday_cached()
设置时间到tv_out中,如果没有,就调用evutil_gettimeofday()
获取实际的时间。
当libevent执行回调的时候,这个时间就被缓存了,所以回调中获取时间会有一点不太准确,尤其是我们的回调执行时间太长,则更不准确,可以调用API强制更新时间:
int event_base_update_cache_time(struct event_base *base);
输出event_base
的状态
void event_base_dump_events(struct event_base *base, FILE *f);
有时候为了调试,我们需要知道当前event中的状态,调用这个API就可以把event信息输出到一个文件中。
让每个事件都执行一个函数
typedef int (*event_base_foreach_event_cb)(const struct event_base *,
const struct event *, void *);
int event_base_foreach_event(struct event_base *base,
event_base_foreach_event_cb fn,
void *arg);
这个函数可以让event_base
中关联的所有事件,不管是激活的,还是挂起的,都执行一个函数。第三个参数会被传递到每一个回调函数中。
回调函数必须返回0继续执行,或是其他数字,结束递归。不管回调函数返回什么,event_base_foreach_function()
都必须要返回。
在回调函数中不能对event和event_base做任何操作,不能修改、增加、删除等,不然会有无法预料的错误。
在event_base_foreach_event()
调用起见,event base会被锁住,其他所有调用event base的线程都会被阻塞,所以确保你的操作不要暂用太多时间。
废弃的event loop函数
Current function | Obsolete current-base version |
---|---|
event_base_dispatch() | event_dispatch() |
event_base_loop() | event_loop() |
event_base_loopexit() | event_loopexit() |
event_base_loopbreak() | event_loopbreak() |