原文链接
libevent采用的是Reactor模式,一种事件列表机制,应用程序需要提供相应的接口并注册到Reactor上,如果相应的时间发生,Reactor将主动调用应用程序注册的接口,这些接口又称为“回调函数”。
了解过异步模式的应该知道,异步模式一般也都有回调函数,但在这里,Reactor不是异步的,Reactor更像是一种同步模式框架,尽管可以同时接收多个服务请求,并且依次同步的处理它们的事件驱动程序,实际上数据拷贝还是阻塞的
由于Reactor模式不是异步的,所以可以预见,如果Reactor处理耗时长的操作会造成事件分发的阻塞,影响到后续事件的处理;(Proactor是异步的)
event_dispatch-->event_base_loop
int
event_base_loop(struct event_base *base, int flags)
{
const struct eventop *evsel = base->evsel;
struct timeval tv;
struct timeval *tv_p;
int res, done;
/* clear time cache */
base->tv_cache.tv_sec = 0;
if (base->sig.ev_signal_added)
evsig_base = base; //处理signal时,指名所属的base实例
done = 0;
#ifndef _EVENT_DISABLE_THREAD_SUPPORT
base->th_owner_id = EVTHREAD_GET_ID();
#endif
while (!done) {
/* Terminate the loop if we have been asked to */
if (base->event_gotterm) {
base->event_gotterm = 0;
break;
}
if (base->event_break) {
base->event_break = 0;
break;
}
/* You cannot use this interface for multi-threaded apps */
while (event_gotsig) {
event_gotsig = 0;
if (event_sigcb) {
res = (*event_sigcb)(); //信号处理回调
if (res == -1) {
errno = EINTR; //错误代码,信号中断
return (-1);
}
}
}
//校正系统时间
timeout_correct(base, &tv);
// 根据 timer heap中事件的最小超时时间,计算系统I/O demultiplexer 的最大等待时间
tv_p = &tv;
if (!base->event_count_active && !(flags & EVLOOP_NONBLOCK)) {
timeout_next(base, &tv_p);
} else {
/* 依然有未处理的就绪时间,就让I/O demultiplexer立即返回
* if we have active events, we just poll new events
* without waiting.
*/
evutil_timerclear(&tv);
}
/* 如果当前没有注册事件,就退出 If we have no events, we just exit */
if (!event_haveevents(base) && !base->event_count_active) {
event_debug(("%s: no events registered.", __func__));
return (1);
}
/* 更新last wait time,update last old time */
gettime(base, &base->event_tv);
/* clear time cache */
base->tv_cache.tv_sec = 0;
// 调用系统I/O demultiplexer等待就绪I/O events,可能是epoll_wait,或者select等;
// 在evsel->dispatch()中,会把就绪signal event、I/O event插入到激活链表中
res = evsel->dispatch(base, tv_p);
if (res == -1)
return (-1);
gettime(base, &base->tv_cache);// 将time cache赋值为当前系统时间
timeout_process(base);/*检查heap中的timer events,将就绪的timer event从heap上删除
并插入到激活链表中*/
// 该函数会寻找最高优先级(priority值越小优先级越高)的激活事件链表,处理就绪事件
if (base->event_count_active) {
event_process_active(base); //处理激活链表中的就绪event
if (!base->event_count_active && (flags & EVLOOP_ONCE))
done = 1;
} else if (flags & EVLOOP_NONBLOCK)
done = 1;
}
/* clear time cache */
base->tv_cache.tv_sec = 0;
event_debug(("%s: asked to terminate loop.", __func__));
return (0);
}
注释:
event_sigcb与event_gotsig
为了避免信号竞争,事件API提供了两程变量:event_sigcb 和 event_gotsig.
某个信号的句柄设置event_gotsig表示收到信号
应用程序把event_sigcb设置成一个回调函数.当信号句柄设置了
event_gotsig之后,event_dispatch函数会执行回调函数处理接收到的信号.
当没有事件注册时回调函数返回1.
回调函数可以返回-1表示错误,这将导致event_dispatch()结束,错误代码为EINTR.
1.2 标志符的说明
在event.h文件中,进行相应的标志符定义
#define EVLOOP_ONCE 0x01 /**< Block at most once. */
#define EVLOOP_NONBLOCK 0x02 /**< Do not block. */
...
#define EV_TIMEOUT 0x01
#define EV_READ 0x02
#define EV_WRITE 0x04
#define EV_SIGNAL 0x08
...
1.3 timeout_correct
如果使用的是非monotonic时间,进行检查校正
static void
timeout_correct(struct event_base *base, struct timeval *tv){}
1.4 timeout_process
static void
timeout_process(struct event_base *base)
{
struct timeval now;
struct event *ev;
EVBASE_ACQUIRE_LOCK(base, EVTHREAD_WRITE, th_base_lock);
if (min_heap_empty(&base->timeheap)) {
EVBASE_RELEASE_LOCK(base, EVTHREAD_WRITE, th_base_lock);
return;
}
gettime(base, &now);
while ((ev = min_heap_top(&base->timeheap))) {
if (evutil_timercmp(&ev->ev_timeout, &now, >))
break;
/* delete this event from the I/O queues */
event_del_internal(ev);
event_debug(("timeout_process: call %p",ev->ev_callback));
event_active_internal(ev, EV_TIMEOUT, 1);
}
EVBASE_RELEASE_LOCK(base, EVTHREAD_WRITE, th_base_lock);
}
Libevent 将 Timer 和 Signal 事件都统一到了系统的 I/O 的 demultiplex 机制中了
首先将 Timer 事件融合到系统 I/O 多路复用机制中,还是相当清晰的,因为系统的 I/O
机制像 select()和 epoll_wait()都允许程序制定一个最大等待时间(也称最大超时时间)
timeout,即使没有 I/O 事件发生,它们也保证能在 timeout 时间内返回。
那么根据所有 Timer 事件的最小超时时间来设置系统 I/O 的 timeout 时间;
当系统I/O返回时,再激活所有就绪的Timer事件就可以了,这样就能将Timer事件完美的融合到系统
的 I/O 机制中了
为什么要用堆:
libevent的时间管理数据结构之前是红黑树,红黑树的插入,删除,获取最值时间复杂度都是 O(logN),而堆获取最小key 值(小根堆)的复杂度为 O(1); libevent 采用了堆结构。(nginx用的是红黑树)
Signal 是异步事件的经典事例,将 Signal 事件统一到系统的 I/O 多路复用中就不像 Timer
事件那么自然了, Signal 事件的出现对于进程来讲是完全随机的,进程不能只是测试一个变
量来判别是否发生了一个信号,而是必须告诉内核“在此信号发生时,请执行如下的操作”。
如果当 Signal 发生时,并不立即调用 event 的 callback 函数处理信号,而是设法通知系
统的 I/O 机制,让其返回,然后再统一和 I/O 事件以及 Timer 一起处理,不就可以了嘛。
是的,这也是 libevent 中使用的方法。
问题的核心在于,当 Signal 发生时,如何通知系统的 I/O 多路复用机制,这里先买个小
关子,放到信号处理一节再详细说明,我想读者肯定也能想出通知的方法,比如使用 pipe。
创建好Socket pair,为socket pair的读socket在libevent的event_base实例上注册一个persist的读事件
Libevent 会在事件主循环中检查标记,来确定是否有触发的 signal
以 Epoll 为例,在epoll_dispatch()函数中
res = epoll_wait(epollop->epfd, events, epollop->nevents, timeout);
if (res == -1) {
if (errno != EINTR) {
event_warn("epoll_wait");
return (-1);
}
//信号中断错误EINTR,可以忽略这种错误
evsig_process(base);
return (0);
} else if (base->sig.evsig_caught) {
evsig_process(base);
}
其中,evsig_process(base)是处理signal事件函数
static void
evsig_handler(int sig)
{
int save_errno = errno;
if (evsig_base == NULL) {
event_warn(
"%s: received signal %d, but have no base configured",
__func__, sig);
return;
}
// 记录信号sig的触发次数,并设置event触发标记
evsig_base->sig.evsigcaught[sig]++;
evsig_base->sig.evsig_caught = 1;
#ifndef _EVENT_HAVE_SIGACTION
signal(sig, evsig_handler);//重新注册信号
#endif
/*向写socket写一个字节数据,触发event_base的I/O事件,从而通知其有信号触发,需要处理*/
send(evsig_base->sig.ev_signal_pair[0], "a", 1, 0);
errno = save_errno;
}