libevent源码解析:定时器事件(三)
文章目录
- 前言
- 一、用例
- 小根堆管理定时器事件
- 小根堆和链表管理定时器事件
- 区别
- 二、基本数据结构介绍
- 结构体成员分析
- 小根堆和链表common_timeout图示
- 三、源码分析
- 小根堆管理定时器事件
- event_new
- event_add
- event_dispatch
- 链表common_timeout管理定时器事件
- event_base_init_common_timeout
- event_add
- event_dispatch
- 总结
前言
libevent中对三类事件进行了封装,io事件、信号事件、定时器事件,libevent源码分析系列文章会分别分析这三类事件,本文分析定时器事件。
本文通过例子展现了两种定时器的使用方法,同时通过源码分析了两种定时器事件的原理。
一、用例
小根堆管理定时器事件
#include <event.h>
void time_cb(evutil_socket_t s,short w, void *arg)
{printf("====time_cb======\n");
}
一个单独的定时器事件
int main()
{timeval t = {0,1};event_base *base = event_base_new(); //初始化reactor对象,epoll_createevent* ev_t = evtimer_new(base, time_cb, NULL);//初始化一个定时器事件evtimer_add(ev_t,&t); //将定时器事件注册到reactor中event_base_dispatch(base); //事件主循环,epoll_wait
}
一个fd的超时事件:如果fd在设定的超时时间没有数据到达,则也会触发回调函数
int main()
{event_base *base = event_base_new(); //初始化reactor对象,epoll_createevent *ev_t = event_new(base,sock,EV_READ|EV_TIMEOUT,time_cb,base); //初始化一个fd的超时事件timeval t = {0,1};event_add(ev_t,&t); //将事件加入io事件队列中并且加入到定时器结构中event_base_dispatch(base); //时间主循环,epoll_wait
}
小根堆和链表管理定时器事件
int main()
{timeval t = {0,1};event_base *base = event_base_new(); //初始化reactor对象,epoll_createevent* ev_t = evtimer_new(base, time_cb, NULL);//初始化一个定时器事件event_base_init_common_timeout(base,&t); //将超时时间标记上common_timeout标记evtimer_add(ev_t,&t); //将定时器事件注册到reactor中event_base_dispatch(base); //事件主循环,epoll_wait
}
区别
小根堆是用在:多个超时event的相对超时时间是随机的。而common-timeout则是用在: 大量的超时event具有相同的相对超时时间。绝对超时时间 = 相对超时时间+ 调用event_add时间。
原因:
小根堆每次删除堆顶超时的event时间复杂度只需要O(logn),假设有m个event超时了需要同时处理,需要花费的时间就是O(mlogn);如果有大量相同的超时时长,并且绝对超时时间一致,使用小根堆和链表则需要花费的时间是O(m+logn),能够提升超时时间处理效率。
二、基本数据结构介绍
结构体成员分析
typedef struct min_heap
{struct event** p;unsigned n, a;
} min_heap_t; //小根堆struct common_timeout_list {struct event_list events; //相同相对超时时间的事件组成链表struct timeval duration; //该链表的相对超时时间struct event timeout_event; //同一链表中绝对超时时间最小的事件struct event_base *base;
};struct event {
...union {TAILQ_ENTRY(event) ev_next_with_common_timeout; //超时链表的节点int min_heap_idx; } ev_timeout_pos;
...union {struct {TAILQ_ENTRY(event) ev_io_next; //io事件队列的节点struct timeval ev_timeout; //相对超时时间} ev_io;
...} _ev;
...struct timeval ev_timeout; //绝对超时时间
};struct event_base {
...struct common_timeout_list **common_timeout_queues; //存放超时链表的数组int n_common_timeouts; //使用的数组大小int n_common_timeouts_allocated; //总共分配的数组大小struct timeval event_tv; //存放上次的时间,用于检测当前系统的时间是否回拨,当前时间如果小于这个时间则表示系统时间回拨了struct min_heap timeheap; //最小堆struct timeval tv_cache; //存放时间的缓存,用于在很短时间中大量获取当前系统时间减少系统调用};
小根堆和链表common_timeout图示
timeheap:按照绝对超时时间组织起来的小根堆,堆顶为最早过期的定时事件;common_timeout_queues将具有相同相对超时时间的超时事件组织成同一链表,如a0 a1 a2具有相同的相对超时时间,同时链表按照绝对超时时间进行排序,a0 a1 a2中的绝对超时时间依次增大或者相同,所以链表中从左到右依次过期,将链表中最早过期的超时事件放入小根堆中。
三、源码分析
小根堆管理定时器事件
主要解分析使用小根堆管理定时器事件的详细流程,占时忽略common_timeout这种情况。
event_new
初始化一个超时事件
int
event_assign(struct event *ev, struct event_base *base, evutil_socket_t fd, short events, void (*callback)(evutil_socket_t, short, void *), void *arg)
{if (!base)base = current_base;ev->ev_flags = EVLIST_INIT;
...if (events & EV_SIGNAL) {...ev->ev_closure = EV_CLOSURE_SIGNAL;} else {if (events & EV_PERSIST) {evutil_timerclear(&ev->ev_io_timeout);ev->ev_closure = EV_CLOSURE_PERSIST;} else {ev->ev_closure = EV_CLOSURE_NONE; //事件结束关闭的标志}}min_heap_elem_init(ev); //初始化最小堆
...return 0;
}
event_add
将该超时事件加入到注册事件队列、(io事件队列)、最小堆中
static inline int
event_add_internal(struct event *ev, const struct timeval *tv,int tv_is_absolute)
{struct event_base *base = ev->ev_base;if (tv != NULL && !(ev->ev_flags & EVLIST_TIMEOUT)) {if (min_heap_reserve(&base->timeheap,1 + min_heap_size(&base->timeheap)) == -1) //给最小堆分配一个存放定时事件的空间return (-1); /* ENOMEM == errno */}
...if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) &&!(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) {if (ev->ev_events & (EV_READ|EV_WRITE))res = evmap_io_add(base, ev->ev_fd, ev); //EV_READ|EV_TIMEOUT 这种参数会将定时器事件插入io事件队列中else if (ev->ev_events & EV_SIGNAL)res = evmap_signal_add(base, (int)ev->ev_fd, ev);if (res != -1)event_queue_insert(base, ev, EVLIST_INSERTED); //定时器事件插入注册事件队列中
...}if (res != -1 && tv != NULL) { //设置了超时时间struct timeval now;int common_timeout;if (ev->ev_closure == EV_CLOSURE_PERSIST && !tv_is_absolute) ev->ev_io_timeout = *tv; //设置io事件的相对超时时间if (ev->ev_flags & EVLIST_TIMEOUT) { //该超时时间已经被添加过一次/* XXX I believe this is needless. */if (min_heap_elt_is_top(ev))notify = 1;event_queue_remove(base, ev, EVLIST_TIMEOUT); //从定时器容器中移除定时事件}if ((ev->ev_flags & EVLIST_ACTIVE) &&(ev->ev_res & EV_TIMEOUT)) {
... //如果该事件是激活事件并且是超时事件,则从激活事件队列中删除该事件event_queue_remove(base, ev, EVLIST_ACTIVE); }gettime(base, &now); //获取当前时间common_timeout = is_common_timeout(tv, base); //时间带有common_timeout标记if (tv_is_absolute) {ev->ev_timeout = *tv; //绝对超时时间直接赋值} else if (common_timeout) {
...} else { //传入的时间是相对超时时间evutil_timeradd(&now, tv, &ev->ev_timeout); //利用相对超时时间更新当前的绝对超时时间}event_queue_insert(base, ev, EVLIST_TIMEOUT); //将定时器事件加入到小根堆中,ev_flags设置成EV_TIMEOUTif (common_timeout) {
...} else {if (min_heap_elt_is_top(ev))notify = 1;}}
...return (res);
}
//事件加入到注册事件队列和最小堆中
static void
event_queue_insert(struct event_base *base, struct event *ev, int queue)
{
...switch (queue) {case EVLIST_INSERTED: //定时器事件加入到注册事件队列当中TAILQ_INSERT_TAIL(&base->eventqueue, ev, ev_next);break;case EVLIST_ACTIVE:base->event_count_active++;TAILQ_INSERT_TAIL(&base->activequeues[ev->ev_pri],ev,ev_active_next);break;case EVLIST_TIMEOUT: {if (is_common_timeout(&ev->ev_timeout, base)) {
...} elsemin_heap_push(&base->timeheap, ev); //定时器事件加入最小堆中break;}default:event_errx(1, "%s: unknown queue %x", __func__, queue);}
}
event_dispatch
从最小堆中获取超时时间最短的绝对超时事件,得到相对超时时间加入epoll_wait中,epoll_wait返回超时的事件加入激活事件队列当中,然后挨着处理激活队列中的激活事件。如果超时事件有persist标记则需要重新更新时间然后加入到最小堆,注册事件队列、(io事件队列)中,等待下一次的超时触发;如果没有则从最小堆、注册事件队列、(Io事件队列)中删除该事件,该事件只触发一次。
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, retval = 0;
...clear_time_cache(base); //清空缓存的时间,让timeout_correct调用gettimeofday系统调用获取到当前时间
...base->event_gotterm = base->event_break = 0;while (!done) {
...timeout_correct(base, &tv); //通过获取到的当前时间和上次保存的时间判断时间是否回拨,没有回拨则直接保存当前时间用于下次比较,回拨了则要更新最小堆和通用定时器中所有事件的超时时间tv_p = &tv;if (!N_ACTIVE_CALLBACKS(base) && !(flags & EVLOOP_NONBLOCK)) {timeout_next(base, &tv_p);//激活队列中没有激活事件,则从小根堆中获得绝对超时时间最短的超时事件,计算出相对超时时间,epoll_wait等待这个相对超时时间后返回,这个超时事件就会放入激活事件队列中} else {evutil_timerclear(&tv); //如果激活队列中有事件则tv设置成0,让epoll_wait立即返回,马上处理还没有处理完的事件}/* If we have no events, we just exit */if (!event_haveevents(base) && !N_ACTIVE_CALLBACKS(base)) { //注册事件队列和激活事件队列都没有事件则直接退出event_debug(("%s: no events registered.", __func__));retval = 1;goto done;}/* update last old time */gettime(base, &base->event_tv); //获取当前时间更新event_tv(用于判断系统时间是否回拨)clear_time_cache(base); //清空时间缓存res = evsel->dispatch(base, tv_p); //调用epoll_wait等待最短的相对超时时间,返回后一定有至少一个超时时间超时了if (res == -1) {event_debug(("%s: dispatch returned unsuccessfully.",__func__));retval = -1;goto done;}update_time_cache(base); //获取当前时间作为缓存时间timeout_process(base); //超时事件加入激活事件队列中,同时从注册事件队列、最小堆、(io事件队列)中删除该事件if (N_ACTIVE_CALLBACKS(base)) {int n = event_process_active(base); //处理激活事件的回调函数,如果是一次性定时器事件则直接调用回调函数,如果是重复的定时器事件则还需要更新定时器事件的时间,然后加入注册事件队列,最小堆,(io事件队列)中if ((flags & EVLOOP_ONCE)&& N_ACTIVE_CALLBACKS(base) == 0&& n != 0)done = 1;} else if (flags & EVLOOP_NONBLOCK)done = 1;}done:clear_time_cache(base);base->running_loop = 0;EVBASE_RELEASE_LOCK(base, th_base_lock);return (retval);
}
//调用epoll_wait等待最短的超时时间
static int
epoll_dispatch(struct event_base *base, struct timeval *tv)
{struct epollop *epollop = base->evbase;struct epoll_event *events = epollop->events;int i, res;long timeout = -1;if (tv != NULL) {timeout = evutil_tv_to_msec(tv); //转换成时间戳if (timeout < 0 || timeout > MAX_EPOLL_TIMEOUT_MSEC) { //时间戳大于临界值/* Linux kernels can wait forever if the timeout is* too big; see comment on MAX_EPOLL_TIMEOUT_MSEC. */timeout = MAX_EPOLL_TIMEOUT_MSEC;}}
...res = epoll_wait(epollop->epfd, events, epollop->nevents, timeout);//如果有超时事件则等待timeout返回,如果没有timeout=-1则一直等到fd有事件才会返回if (res == -1) {if (errno != EINTR) {event_warn("epoll_wait");return (-1);}return (0);}
...return (0);
}
//将超时事件加入到激活事件队列中
static void
timeout_process(struct event_base *base)
{/* Caller must hold lock. */struct timeval now;struct event *ev;if (min_heap_empty(&base->timeheap)) { //没有超时事件直接返回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); //从最小堆、注册事件队列、(激活事件队列)、(io事件队列)中删除超时事件event_active_nolock(ev, EV_TIMEOUT, 1); //定时事件加入到激活事件队列中}
}
//处理超时事件
static int
event_process_active(struct event_base *base)
{/* Caller must hold th_base_lock */struct event_list *activeq = NULL;int i, c = 0;for (i = 0; i < base->nactivequeues; ++i) {if (TAILQ_FIRST(&base->activequeues[i]) != NULL) { //从index=0开始遍历激活事件队列base->event_running_priority = i;activeq = &base->activequeues[i]; //获得某个index对应的链表c = event_process_active_single_queue(base, activeq); //挨着处理链表中所有激活事件的回调函数if (c < 0) {base->event_running_priority = -1;return -1;} else if (c > 0)break; }}event_process_deferred_callbacks(&base->defer_queue,&base->event_break);base->event_running_priority = -1;return c;
}static int
event_process_active_single_queue(struct event_base *base,struct event_list *activeq)
{struct event *ev;int count = 0;EVUTIL_ASSERT(activeq != NULL);for (ev = TAILQ_FIRST(activeq); ev; ev = TAILQ_FIRST(activeq)) {if (ev->ev_events & EV_PERSIST)event_queue_remove(base, ev, EVLIST_ACTIVE); //有persist标记只移除激活事件队列中的事件elseevent_del_internal(ev); //否则要移除激活事件队列,注册事件队列,(io事件队列)中对应的事件if (!(ev->ev_flags & EVLIST_INTERNAL))++count;
...switch (ev->ev_closure) {case EV_CLOSURE_SIGNAL:event_signal_closure(base, ev);break;case EV_CLOSURE_PERSIST: //重复定时器事件event_persist_closure(base, ev);//执行定时事件回调函数,同时将定时事件更新时间后重新加入注册事件队列、最小堆、(io事件队列)中break;default:case EV_CLOSURE_NONE: //一次性定时器事件EVBASE_RELEASE_LOCK(base, th_base_lock);(*ev->ev_callback)(ev->ev_fd, ev->ev_res, ev->ev_arg);//执行定时事件的回调函数break;}
...}return count;
}static inline void
event_persist_closure(struct event_base *base, struct event *ev)
{if (ev->ev_io_timeout.tv_sec || ev->ev_io_timeout.tv_usec) {struct timeval run_at, relative_to, delay, now;ev_uint32_t usec_mask = 0;EVUTIL_ASSERT(is_same_common_timeout(&ev->ev_timeout,&ev->ev_io_timeout));gettime(base, &now);if (is_common_timeout(&ev->ev_timeout, base)) {
...} else {delay = ev->ev_io_timeout; //相对超时时间if (ev->ev_res & EV_TIMEOUT) {relative_to = ev->ev_timeout; //获得绝对超时时间} else {relative_to = now;}}evutil_timeradd(&relative_to, &delay, &run_at); //该事件过期的绝对超时时间if (evutil_timercmp(&run_at, &now, <)) { //该事件的绝对超时时间<当前时间,证明该事件是超时触发的,则重新更新超时时间;如果不是超时触发的则沿用上之前的绝对超时时间evutil_timeradd(&now, &delay, &run_at); //获取下一次的绝对超时时间}run_at.tv_usec |= usec_mask;event_add_internal(ev, &run_at, 1); //重新将事件加入注册事件队列、最小堆、(io事件队列)中}EVBASE_RELEASE_LOCK(base, th_base_lock);(*ev->ev_callback)(ev->ev_fd, ev->ev_res, ev->ev_arg);//调用回调函数
}
链表common_timeout管理定时器事件
event_base_init_common_timeout
event_base_init_common_timeout将事件增加common_timeout标记,调用event add时就会将超时时间加入最小堆的同时加入到链表当中。
原理:
对一个struct timeval结构体,成员tv_usec的单位是微秒,所以最大也就是999999,只需低20比特位就能存储了。但成员tv_usec的类型是int或者long,肯定有32比特位。所以,就有高12比特位是空闲的。
Libevent就是利用那空闲的12个比特位做文章的。这12比特位是高比特位。Libevent使用最高的4比特位作为标志位,标志它是一个专门用于common-timeout的时间,下文将这个标志称为common-timeout标志。次8比特位用来记录该超时时长在common_timeout_queues数组中的位置,即下标值。这也限制common_timeout_queues数组的长度,最大为2的8次方,即256。
//将时间增加timeout标记和索引,并根据该时间初始化一个common_timeout_list链表,同时初始化一个内部超时时间timeout_event
const struct timeval *
event_base_init_common_timeout(struct event_base *base,const struct timeval *duration)
{int i;struct timeval tv;const struct timeval *result=NULL;struct common_timeout_list *new_ctl;EVBASE_ACQUIRE_LOCK(base, th_base_lock);if (duration->tv_usec > 1000000) { //tv_usec进位到tv_secmemcpy(&tv, duration, sizeof(struct timeval));if (is_common_timeout(duration, base))tv.tv_usec &= MICROSECONDS_MASK; tv.tv_sec += tv.tv_usec / 1000000;tv.tv_usec %= 1000000;duration = &tv;}for (i = 0; i < base->n_common_timeouts; ++i) { //判断是否为这个相对超时时间分配过存放链表的空间const struct common_timeout_list *ctl =base->common_timeout_queues[i];if (duration->tv_sec == ctl->duration.tv_sec &&duration->tv_usec ==(ctl->duration.tv_usec & MICROSECONDS_MASK)) { //&操作去掉common_timeout标记,比较分配空间的相对超时时间和传入的相对超时时间是否一致EVUTIL_ASSERT(is_common_timeout(&ctl->duration, base));result = &ctl->duration; //一致则直接返回分配过的带有common_timeout标记的相对超时时间goto done;}}
...if (base->n_common_timeouts_allocated == base->n_common_timeouts) { //数组分配的空间和使用空间相等了int n = base->n_common_timeouts < 16 ? 16 : //重新分配2倍的空间base->n_common_timeouts*2;struct common_timeout_list **newqueues =mm_realloc(base->common_timeout_queues,n*sizeof(struct common_timeout_queue *));if (!newqueues) {event_warn("%s: realloc",__func__);goto done;}base->n_common_timeouts_allocated = n;base->common_timeout_queues = newqueues;}new_ctl = mm_calloc(1, sizeof(struct common_timeout_list)); //分配存放链表节点的空间if (!new_ctl) {event_warn("%s: calloc",__func__);goto done;}TAILQ_INIT(&new_ctl->events);new_ctl->duration.tv_sec = duration->tv_sec;new_ctl->duration.tv_usec =duration->tv_usec | COMMON_TIMEOUT_MAGIC |(base->n_common_timeouts << COMMON_TIMEOUT_IDX_SHIFT); //给链表节点的时间增加timeout标记和索引位置的标记evtimer_assign(&new_ctl->timeout_event, base,common_timeout_callback, new_ctl); //初始化链表中绝对超时时间最小的事件,设置回调common_timeout_callbacknew_ctl->timeout_event.ev_flags |= EVLIST_INTERNAL; //内部事件event_priority_set(&new_ctl->timeout_event, 0); //最高优先级new_ctl->base = base;base->common_timeout_queues[base->n_common_timeouts++] = new_ctl; //链表节点加入数组中result = &new_ctl->duration; //返回带有标记的时间
done:return result;
}//这时内部超时时间timeout_event触发的,遍历对应的common_timeout_list中的事件,加入激活事件队列中,更新时间重新将timeout_event事件加入最小堆中
static void
common_timeout_callback(evutil_socket_t fd, short what, void *arg)
{struct timeval now;struct common_timeout_list *ctl = arg;struct event_base *base = ctl->base;struct event *ev = NULL;EVBASE_ACQUIRE_LOCK(base, th_base_lock);gettime(base, &now);while (1) {//挨着遍历timeout_event对应的链表ev = TAILQ_FIRST(&ctl->events);if (!ev || ev->ev_timeout.tv_sec > now.tv_sec ||(ev->ev_timeout.tv_sec == now.tv_sec &&(ev->ev_timeout.tv_usec&MICROSECONDS_MASK) > now.tv_usec))break; //链表中的事件没有超时则直接退出,因为链表按绝对超时时间升序排列event_del_internal(ev); //从common_timeout_list中删除对应的事件,如果设置成persist标记,后面更新了事件就会重新添加该事件event_active_nolock(ev, EV_TIMEOUT, 1); //将common_timeout_list中的事件加入到激活事件队列中}if (ev)common_timeout_schedule(ctl, &now, ev); //如果链表中还有事件没发生,则重新将该链表的timeout_event事件加入到最小堆中,事件为当前链表头结点的绝对时间EVBASE_RELEASE_LOCK(base, th_base_lock);
}
event_add
将超时事件加入到对应的common_timeout_list中,同时将timeout_event事件加入到最小堆中
static inline int
event_add_internal(struct event *ev, const struct timeval *tv,int tv_is_absolute)
{struct event_base *base = ev->ev_base;if (tv != NULL && !(ev->ev_flags & EVLIST_TIMEOUT)) {if (min_heap_reserve(&base->timeheap,1 + min_heap_size(&base->timeheap)) == -1) //给最小堆分配一个存放定时事件的空间return (-1); /* ENOMEM == errno */}
...common_timeout = is_common_timeout(tv, base); //时间带有common_timeout标记if (tv_is_absolute) {ev->ev_timeout = *tv; //绝对超时时间直接赋值} else if (common_timeout) {struct timeval tmp = *tv;tmp.tv_usec &= MICROSECONDS_MASK; //去除标记取真正的时间evutil_timeradd(&now, &tmp, &ev->ev_timeout); //算得绝对超时时间ev->ev_timeout.tv_usec |=(tv->tv_usec & ~MICROSECONDS_MASK); //绝对超时时间增加标记} else {evutil_timeradd(&now, tv, &ev->ev_timeout); //利用相对超时时间更新当前的绝对超时时间}event_queue_insert(base, ev, EVLIST_TIMEOUT); //将定时器事件加入到common_timeout容器中if (common_timeout) {struct common_timeout_list *ctl =get_common_timeout_list(base, &ev->ev_timeout); //获取当前时间的common_timeout_listif (ev == TAILQ_FIRST(&ctl->events)) { //从链表中取出头节点,绝对超时时间最小的事件common_timeout_schedule(ctl, &now, ev); //设置timeout_event的超时时间加入到最小堆中}} else {if (min_heap_elt_is_top(ev))notify = 1;}}
...return (res);
}static void
event_queue_insert(struct event_base *base, struct event *ev, int queue)
{
...switch (queue) {
...case EVLIST_TIMEOUT: {if (is_common_timeout(&ev->ev_timeout, base)) { //如果超时时间有common_timeout标记struct common_timeout_list *ctl =get_common_timeout_list(base, &ev->ev_timeout); //根据时间获取对应的common_timeout_listinsert_common_timeout_inorder(ctl, ev); //定时器事件插入链表common_timeout_list中} elsemin_heap_push(&base->timeheap, ev); //定时器事件加入最小堆中break;}default:event_errx(1, "%s: unknown queue %x", __func__, queue);}
}static void
insert_common_timeout_inorder(struct common_timeout_list *ctl,struct event *ev)
{struct event *e;//遍历链表,按照绝对超时时间从小到大插入链表中,一般情况下直接插入到链表的尾部,但是在多线程中可能出现先调用evutil_timeradd的还没来的及插入,后面的先插入了,所以需要遍历。TAILQ_FOREACH_REVERSE(e, &ctl->events,event_list, ev_timeout_pos.ev_next_with_common_timeout) {EVUTIL_ASSERT(is_same_common_timeout(&e->ev_timeout, &ev->ev_timeout));if (evutil_timercmp(&ev->ev_timeout, &e->ev_timeout, >=)) {TAILQ_INSERT_AFTER(&ctl->events, e, ev,ev_timeout_pos.ev_next_with_common_timeout);//从队列后面插入return; //插入了直接返回}}//新插入的绝对超时时间最小则从头部插入TAILQ_INSERT_HEAD(&ctl->events, ev,ev_timeout_pos.ev_next_with_common_timeout);
}static void
common_timeout_schedule(struct common_timeout_list *ctl, const struct timeval *now, struct event *head)
{ struct timeval timeout = head->ev_timeout; timeout.tv_usec &= MICROSECONDS_MASK; //清除common-timeout标志 //用common_timeout_list结构体的一个event成员作为超时event调用event_add_internal //由于已经清除了common-timeout标志,所以这次将最小超时事件timeout_event插入到小根堆中。 event_add_internal(&ctl->timeout_event, &timeout, 1);
}
event_dispatch
第一个循环是内部事件timeout_event事件触发,调用timeout_event_callback回调函数,该回调函数遍历common_timeout_list将触发的事件加入到激活事件队列中,第二个循环,激活队列中有事件则马上处理common_timeout_list中的事件,也就是外部注册的事件。
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, retval = 0;while (!done) {
...tv_p = &tv;if (!N_ACTIVE_CALLBACKS(base) && !(flags & EVLOOP_NONBLOCK)) {timeout_next(base, &tv_p); //最小堆中的timeout_event中取出最短的相对超时时间} else {evutil_timerclear(&tv); //执行完内部超时时间timeout_event中的回调函数,会将common_timeout_list中的事件加入激活队列中,所以激活队列中还有事件则tv设置成0,让epoll_wait马上返回执行激活事件队列中的事件}res = evsel->dispatch(base, tv_p); //调用epoll_wait等待最短的相对超时时间,有一个timeout_event事件返回if (res == -1) {event_debug(("%s: dispatch returned unsuccessfully.",__func__));retval = -1;goto done;}timeout_process(base); //第一次timeout_event事件加入激活事件队列中,并从最小堆中删除该事件,第二次由于最小堆的timeout_event事件没有超时这个函数直接返回if (N_ACTIVE_CALLBACKS(base)) {int n = event_process_active(base); //第一次调用timeout_event事件的回调函数,第二次调用用户注册的超时时间的回调函数if ((flags & EVLOOP_ONCE)&& N_ACTIVE_CALLBACKS(base) == 0&& n != 0)done = 1;} else if (flags & EVLOOP_NONBLOCK)done = 1;}
done:clear_time_cache(base);base->running_loop = 0;EVBASE_RELEASE_LOCK(base, th_base_lock);return (retval);
}timeout_process(struct event_base *base)
{
...while ((ev = min_heap_top(&base->timeheap))) {if (evutil_timercmp(&ev->ev_timeout, &now, >))break;event_del_internal(ev); //timeout_event事件触发从最小堆中删除,然后common_timeou_list中的事件触发则从common_timeout_list链表中移除事件event_debug(("timeout_process: call %p",ev->ev_callback));event_active_nolock(ev, EV_TIMEOUT, 1); //第一次将timeout_event事件加入激活事件队列中,然后timeout_event_callbak触发后将common_timeout_list中的事件加入到激活事件队列中,这里才是用户注册的超时时间}
}static void
event_queue_remove(struct event_base *base, struct event *ev, int queue)
{
...ev->ev_flags &= ~queue;switch (queue) {
...case EVLIST_TIMEOUT:if (is_common_timeout(&ev->ev_timeout, base)) { //common_timeout_list中链表中的事件struct common_timeout_list *ctl =get_common_timeout_list(base, &ev->ev_timeout);TAILQ_REMOVE(&ctl->events, ev,ev_timeout_pos.ev_next_with_common_timeout); //移除激活的common_timeout事件,也就是链表的首部} else {min_heap_erase(&base->timeheap, ev); //首先时内部的timout_event事件,从最小堆中删除}break;default:event_errx(1, "%s: unknown queue %x", __func__, queue);}
}
//common_timeout超时时间中设置了persist标记
static inline void
event_persist_closure(struct event_base *base, struct event *ev)
{if (ev->ev_io_timeout.tv_sec || ev->ev_io_timeout.tv_usec) {struct timeval run_at, relative_to, delay, now;ev_uint32_t usec_mask = 0;EVUTIL_ASSERT(is_same_common_timeout(&ev->ev_timeout,&ev->ev_io_timeout));gettime(base, &now);if (is_common_timeout(&ev->ev_timeout, base)) { //调用common_timeout_callback时触发的事件delay = ev->ev_io_timeout; //获得相对超时时间usec_mask = delay.tv_usec & ~MICROSECONDS_MASK; //取消common_timeout标记delay.tv_usec &= MICROSECONDS_MASK; //增加common_timeout标记if (ev->ev_res & EV_TIMEOUT) {relative_to = ev->ev_timeout; //获得绝对超时时间relative_to.tv_usec &= MICROSECONDS_MASK; //增加common_timeout标记} else {relative_to = now;}} else { //timeout_event事件被触发delay = ev->ev_io_timeout;if (ev->ev_res & EV_TIMEOUT) {relative_to = ev->ev_timeout;} else {relative_to = now;}}evutil_timeradd(&relative_to, &delay, &run_at);if (evutil_timercmp(&run_at, &now, <)) {evutil_timeradd(&now, &delay, &run_at); //获得下一次的绝对超时时间}run_at.tv_usec |= usec_mask;event_add_internal(ev, &run_at, 1); //common_timeout_list中的事件触发,重新将事件加入common_timeout_list中}EVBASE_RELEASE_LOCK(base, th_base_lock);(*ev->ev_callback)(ev->ev_fd, ev->ev_res, ev->ev_arg); //common_timeout_list中的事件调用用户注册的回调函数
}
总结
在大多数应用中定时事件规模不大,我们一般是使用小根堆进行定时器事件的管理;libevent通过最小堆加上链表的方式提供了一种管理大规模定时器的高效方法。
相关文章:

libevent源码解析:定时器事件(三)
文章目录 前言一、用例小根堆管理定时器事件小根堆和链表管理定时器事件区别 二、基本数据结构介绍结构体成员分析小根堆和链表common_timeout图示 三、源码分析小根堆管理定时器事件event_newevent_addevent_dispatch 链表common_timeout管理定时器事件event_base_init_common…...

3D资产管理
3D 资产管理是指组织、跟踪、优化和分发 3D 模型和资产以用于游戏、电影、AR/VR 体验等各种应用的过程。 3D资产管理也称为3D内容管理。 随着游戏、电影、建筑、工程等行业中 3D 内容的增长,实施有效的资产管理工作流程对于提高生产力、减少错误、简化工作流程以及使…...

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:Blank)
空白填充组件,在容器主轴方向上,空白填充组件具有自动填充容器空余部分的能力。仅当父组件为Row/Column/Flex时生效。 说明: 该组件从API Version 7开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。 子组件…...

【手游联运平台搭建】游戏平台的作用
随着科技的不断发展,游戏行业也在不断壮大,而游戏平台作为连接玩家与游戏的桥梁,发挥着越来越重要的作用。游戏平台不仅为玩家提供了便捷的游戏体验,还为游戏开发者提供了广阔的市场和推广渠道。本文将从多个方面探讨游戏平台的作…...

手把手教会你 - StreamAPI基本用法
1. 简介 目前响应式编程的学习中很多时候都用到了Lambda表达式和StreamAPI,那么今天就在这里记录一下一些最基本的使用方法。 StreamAPI中引入了流的概念,其将集合看作一种流,流在管道中传输(动态的),可以…...

和为K的子数组
题目: 使用前缀和的方法可以解决这个问题,因为我们需要找到和为k的连续子数组的个数。通过计算前缀和,我们可以将问题转化为求解两个前缀和之差等于k的情况。 假设数组的前缀和数组为prefixSum,其中prefixSum[i]表示从数组起始位…...

Redis:java中redis的基本使用(springboot)
文章目录 springboot中使用redisspringboot 连接 redis三种方式导入依赖增删改查小练习 springboot中使用redis springboot 连接 redis三种方式 jedis (redis官方提供的)springboot自带的redisson (基于jedis优化的,性能最好,使…...

微型计算机技术
摘要:微型计算机是通用计算机的一个重要发展分支,自1981年美国IBM公司推出第一代商用微型计算机以来,微型计算机迅速进入社会各个领域,且技术不断更新、产品快速换代,已成为人们工作和生活中不可缺少的基本工具。 一、微型计算机技术发展历史 1.第一代微处理器(19…...

mysql下载教程
什么是mysql MySQL是一种开源的关系型数据库管理系统,由瑞典MySQL AB公司开发,现在由Oracle公司维护。MySQL支持多个操作系统,包括Linux、Windows、macOS等。它是一种客户端/服务器模式的数据库,提供高效、可靠、稳定的数据存储和…...

ResponseStatusException
目录 概述: 综合实例: 继承 ResponseStatusException-自定义异常类 继承 ResponseStatusException-自定义响应头信息 继承 ResponseStatusException-定制更多异常处理逻辑 继承 ResponseStatusException-根据异常发生的上下文动态改变 HTTP 状态码…...

第五十二回 戴宗二取公孙胜 李逵独劈罗真人-飞桨AI框架安装和使用示例
吴用说只有公孙胜可以破法术,于是宋江请戴宗和李逵去蓟州。两人听说公孙胜的师傅罗真人在九宫县二仙山讲经,于是到了二仙山,并在山下找到了公孙胜的家。 两人请公孙胜去帮助打高唐州,公孙胜说听师傅的。罗真人说出家人不管闲事&a…...

CSAPP-程序的机器级表示
文章目录 概念扫盲思想理解经典好图安全事件 概念扫盲 1.汇编代码使用文本格式,相较于汇编的二进制可读性更好 2.程序内存包括:可执行的机器代码、操作系统需要的信息、管理过程调用和返回的运行时栈、用户分配的内存块 3.链接器为函数调用找到匹配的可…...

TCP传输收发
TCP通信: TCP发端: socket connect send recv close TCP收端: socket bind listen accept send recv close 1.connect int connect(int sockfd, const struct sockaddr *addr, socklen_t ad…...

OJ习题之——圆括号编码
圆括号编码 1.题目描述2.完整代码3.图例演示 1.题目描述 题目描述 令Ss1 s2 …sn是一个规则的圆括号字符串。S以2种不同形式编码: (1)用一个整数序列Pp1 p2 … pn编码,pi代表在S中第i个右圆括号的左圆括号数量。(记为…...

Android耗电分析之Battery Historian工具使用
Battery-Historian是谷歌推出的一款专门分析Bugreport的工具,是谷歌在2015年I/O大会上推出的一款检测运行在android5.0(Lollipop)及以后版本的设备上电池的相关信息和事件的工具,是一款对于分析手机状态,历史运行情况很好的可视化分析工具。 …...

vue el-avatar 使用require提示无法找到图片
报错信息 错误代码 问题分析 vue初始化DOM树时没有挂载数据,导致无法找到模块 解决方案...

深入理解 C# 中的 Task:异步编程的利器
深入理解 C# 中的 Task:异步编程的利器 前言一、Task 的基本概念什么是 Task?为什么要使用 Task? Task 的使用方法创建 Task等待 Task 完成Task 返回结果 Task 的进阶用法Task 异常处理Task 同步执行Task 并发限制 Task 的实际应用场景并行计…...

YOLOv9电动车头盔佩戴检测,详细讲解模型训练
向AI转型的程序员都关注了这个号👇👇👇 一、YOLOv9简介 YOLOv9是YOLO系列算法的最新版本。YOLO系列算法自2015年首次提出以来,已经在目标检测领域取得了显著的进展,以其快速和准确的特点而广受欢迎。 论文地址…...

OpenStack之Nova
一 、Nova 使用OpenStack Compute来托管和管理云计算系统。 OpenStack Compute是基础架构即服务 (IaaS)系统的主要部分。 主要模块在Python中实现: 1因为认证,与OpenStack 身份认证keystone 交互。 2因为磁盘和服务器镜像…...

虽说主业搞前端,看到如此漂亮的网页UI,也是挪不开眼呀。
漂亮的网页UI能够吸引人的眼球,给人留下深刻的印象。作为前端开发人员,可以通过不断学习和掌握设计技巧和工具,提升自己的UI设计能力,为用户提供更好的视觉体验。 以下是一些提升网页UI设计能力的建议: 学习设计基础知…...

嵌入式学习第二十六天!(网络传输:TCP编程)
TCP通信: 1. TCP发端: socket -> connect -> send -> recv -> close 2. TCP收端: socket -> bind -> listen -> accept -> recv -> send -> close 3. TCP需要用到的函数: 1. co…...

【LeetCode】升级打怪之路 Day 14:二叉树的遍历
今日题目: 144. 二叉树的前序遍历94. 二叉树的中序遍历145. 二叉树的后序遍历102. 二叉树的层序遍历107. 二叉树的层序遍历 II199. 二叉树的右视图637. 二叉树的层平均值429. N 叉树的层序遍历515. 在每个树行中找最大值116. 填充每个节点的下一个右侧节点指针117. …...

[Unity实战]使用NavMeshAgent做玩家移动
其实除了Character Controller, Rigidbody,我们还可以使用NavMeshAgent去做。这么做的好处是能避免玩家去莫名其妙的地方(毕竟基于烘焙过的导航网格),一般常见于元宇宙应用和mmo。 根据Unity手册,NavMeshAgent 也有和…...

官网:随便搞个?那不如不搞,搞不好就给公司减分了。
官网建设确实需要认真对待,不能随便搞。一个粗制滥造的官网可能会给公司带来负面影响,降低品牌形象和用户体验。以下是一些官网建设的重要原则: 专业性:官网应该展示公司的专业性和专业知识。它应该以专业的设计、内容和功能来展示…...

Ansible 基础入门
2)Ansible 介绍 Ansible 基本概念 Ansible 是一种自动化运维工具,基于 Paramiko 开发的,并且基于模块化工作,Ansible 是一种集成 IT 系统的配置管理、应用部署、执行特定任务的开源平台,它是基于 Python 语言…...

讨论:5万官网是建站界的劳斯莱斯了吧,到了软件开发领域呢?
如题,所以赛道选择很重要,当然难度系数也不一样。能花5万元做官网的,凤毛麟角,如果是做软件开发,5万元顶多算个起步价,老铁们,是这样吗?...

手写分布式配置中心(三)增加实时刷新功能(短轮询)
要实现配置自动实时刷新,需要改造之前的代码。代码在https://gitee.com/summer-cat001/config-center 服务端改造 服务端增加一个版本号version,新增配置的时候为1,每次更新配置就加1。 Overridepublic long insertConfigDO(…...

【RabbitMQ】WorkQueue
📝个人主页:五敷有你 🔥系列专栏:MQ ⛺️稳中求进,晒太阳 Work Queues Work queues任务模型,简单来说就是让多个消费者绑定到一个队列,共同消费队列中的消息 当消息处理比较耗时的时候&…...

国内免费好用 Chat GPT推荐
无论您是寻找技术洞见还是灵感激发,此网站是您的绝佳去处。探索着名作家的精彩观点和创意解决方案,它不仅是知识的源泉,更是思维的驱动力。在这里,您将发现无尽的学习资源和启发,助您不断前行这是一款基于OpenAi开发的…...

基于springboot实现在线考试系统项目【项目源码+论文说明】
基于springboot实现在线考试系统演示 摘要 时代在变化,科技技术以无法预测的速度在达到新的高度,并且被应用于社会生活的各个领域,随着生活的加快,也使很多潜在的点逐渐突显出来,社会对于人才的要总是非常迫切的&…...