Libevent事件轮询
Toc
  1. 开始轮询
    1. Interface
  2. 停止轮询
    1. Example: Shut down immediately
    2. Example: Run an event loop for 10 seconds, then exit.
  3. 重新检查事件
  4. 检查缓存的时间间隔
  5. 获取event_base的状态
  6. 遍历event_base的每个事件
  7. 废弃的轮询函数

原文:R3: Running an event loop

开始轮询

你创建了一个event_base,往它注册一些事件之后(下节会讲怎么注册事件),你接下来会让Libevent开始等待事件并通知到你。

Interface

#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,也并没有注册的事件。它不断循环检查是否有注册的事件可以触发,比如一个socket已经可以进行读操作,或者某个事件已经超时了。 一旦有这样的事件,它激活这些事件,并开始执行对应的事件。
你可以设置它的第2个参数flag来改变event_base_loop()的默认行为。

  1. 如果设置了EVLOOP_ONCE,那这个函数会一直等待,直到有事件可以激活,激活并执行事件回调,然后才返回,即阻塞式
  2. EVLOOP_NONBLOCK,非阻塞,做检查,有事件可以激活,则激活执行,否则就返回
  3. 一般来说,当没有等待的事件轮询就退出了,但你可以传EVLOOP_NO_EXIT_ON_EMPTY给到flag来改变这一行为,比如,你打算在其他线程里添加事件。 这个值会让event_base_loop一直轮询,直到你调用event_base_loopbreak()event_base_loopexit()来显式退出轮询。

这个函数event_base_loop()轮询完成之后,返回0表示成功,如果因为一些后端函数未能处理的错误而退出,则返回-1.
这里有一个简要算法(Pseudocode)来帮助理解:

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_dispatch()跟调用event_base_loop()一样,不过不能设置flag。 所以,它会一直轮询,直到没有注册的事件,或者你调用了event_base_loopbreak()或者event_base_loopexit()


停止轮询

在注册事件被删除之前,你想停止一个正在执行的轮询,2个有细微区别的函数可以实现:

int event_base_loopexit(struct event_base *base, const struct timeval *tv);
int event_base_loopbreak(struct event_base *base);

函数event_base_loopexit()告诉event_base在一个时间阀值之后停止轮询。如果参数tv为NULL,则event_base的轮询会马上停止。 如果event_base当前已经开始某个事件的回调,那它会等当前的回调函数执行完毕之后再退出。
注意到当轮询正在执行时,event_base_loopexit(base,NULL)event_base_loopbreak(base)的行为有点不一样。 loopexit是在下次轮询完毕之后才退出循环,效果等同使用了EVLOOP_ONCE标识;而loopbreak则只针对当前循环,所以如果调用的时间点不是在一个循环之内,它就没有效果。

这2个函数返回0表示成功,-1表示失败。

Example: Shut down immediately

#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);
}

Example: Run an event loop for 10 seconds, then exit.

#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的轮询是如何退出的:是调用了event_base_dispatch()或者event_base_loop(),还是调用了event_base_loopexit()或者event_base_break()。 你可以使用下面的函数:

int event_base_got_exit(struct event_base *base);
int event_base_got_break(struct event_base *base);

如果是因为event_base_loopexit()退出的,函数event_base_got_exit返回true;而如果是因为event_base_break()退出的,函数event_base_got_break返回true,否则都返回false。在下次轮询开始时,结果会被重置。


重新检查事件

一般说来,Libevent检查事件,激活最高优先级的事件,然后再检查,如此反复。当有时在当前事件回调执行之后你想停止Libevent的轮询,再让它开始。跟event_base_loopbreak()类似,你可以使用:

int event_base_loopcontinue(struct event_base *);

只有在你当前正在执行的事件回调函数中做这个调用才有效果。


检查缓存的时间间隔

有时候你想在一个事件回调函数中查看当前时间,而你又不想调用gettimeofday()(假设这在你的系统中是一个系统调用而你不想做一次系统调用)。 在一个事件回调函数中,你可以通过Libevent的提供的这个函数来获取该次轮询开始的时间。

int event_base_gettimeofday_cached(struct event_base *base,
    struct timeval *tv_out);

如果当前event_base正在调用事件的回调函数,那函数event_base_gettimeofday_cached()会把缓存着的时间设置到参数tv_out返回, 否则它会调用evutil_gettimeofday()来获取当前时间(还是系统调用了)。成功返回0,失败返回负数.
注意到timeval是一个缓存的值,对应的是Libevent开始执行回调函数的时间点,所以它会有一点误差。如果你的回调函数执行了很长事件,那这个值的误差会更大。如果要强制更新这个缓存的时间值,你可以调用:

int event_base_update_cache_time(struct event_base *base);

成功返回0, 失败返回-1, 如果event_base当前不是在轮询,则调用这个函数也是没有效果。


获取event_base的状态

void event_base_dump_events(struct event_base *base, FILE *f);

为了调试你的程序或者调试Libevent,有时你可能想获取当前注册到event_base的事件以及它们的状态。调用event_base_dump_events()会把结果写入到你指定的文件流里。
结果列表有非常好的可读性,它的格式未来版本中可能会改变。


遍历event_base的每个事件

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_foreach_event()来遍历注册到event_base里的那些当前激活或等待的事件。对于每个事件,你提供的回调函数(第2个参数fn)会每次都会执行,但执行顺序是无序的。 第3个参数将传给你的回调函数。
你的回调函数返回0才会继续遍历,其他值则会中断。最后一个回调函数执行返回的结果,将会被event_base_foreach_event()返回。
你的回调函数不能修改它接收到的事件,也不能对event_base添加、修改或删除事件。 否则将会出现不可预测的情况。
当调用event_base_foreach_event()时,它会占用event_base持有的锁,所以调用期间其他线程就无法对这个event_base做其他事件了,你得确保这个遍历不会占用太多时间。


废弃的轮询函数

当前用的轮询相关函数是以event_base_为前缀的,而废弃的函数则是以event_为前缀,少了base。
其他略。



– EOF –

Categories: in_lib
Tags: libevent