Libevent时间管理

Libevent时间管理

#define evtimer_new(b, cb, arg) event_new((b), -1, 0, (cb), (arg))
工作流程
1.设置超时值
static inline int event_add_internal(struct event *ev, const struct timeval *tv, int tv_is_absolute)
{
//该event之前被加入到超时队列。用户可以对同一个event调用多次event_add,并且可以每次都用不同的超时值。
if (ev->ev_flags & EVLIST_TIMEOUT) event_queue_remove(base, ev, EVLIST_TIMEOUT);
//因为可以在次线程调用event_add。而主线程刚好在执行event_base_dispatch
if ((ev->ev_flags & EVLIST_ACTIVE) &&(ev->ev_res & EV_TIMEOUT)) event_queue_remove(base, ev, EVLIST_ACTIVE);
event_queue_insert(base, ev, EVLIST_TIMEOUT);
evthread_notify_base(base);
}
如果是一个超时event,那么是可以多次添加的。并且对应超时值会使用最后添加时指明的那个,之前的统统不要,即替换掉之前的超时值。
在调用event_add时设定的超时值是一个时间段(可以认为隔多长时间就触发一次),相对于现在,即调用event_add的时间,而不是调用event_base_dispatch的时间。

2.调用多路IO复用函数等待超时
event_base_loop函数,处理超时的event
event_base_loop()
{
if(!N_ACTIVE_CALLBACKS(base) && !(flags & EVLOOP_NONBLOCK))
timeout_next(base, &tv_p);
else
evutil_timerclear(&tv);
res = evsel->dispatch(base, tv_p);
//处理超时事件,将超时事件插入到激活链表中
timeout_process(base);
}

//选出超时值最小的那个
static int timeout_next(struct event_base *base, struct timeval **tv_p)
{
// 如果超时时间<=当前时间,不能等待,需要立即返回
// 因为ev_timeout这个时间是由event_add调用时的绝对时间 + 相对时间。所以ev_timeout是
// 绝对时间。可能在调用event_add之后,过了一段时间才调用event_base_diapatch,所以
// 现在可能都过了用户设置的超时时间。
if (evutil_timercmp(&ev->ev_timeout, &now, <=)) {
evutil_timerclear(tv); //清零,这样可以让dispatcht不会等待,马上返回
goto out;
}

// 计算等待的时间=当前时间-最小的超时时间  
evutil_timersub(&ev->ev_timeout, &now, tv); 

}

3.激活超了时的event
//把超时了的event,放到激活队列中。并且,其激活原因设置为EV_TIMEOUT
static void timeout_process(struct event_base *base)
{
event_del_internal(ev);
event_active_nolock(ev, EV_TIMEOUT, 1);
}
就检查时间小根堆,看有多少个event已经超时了。如果超时了,那就把这个event加入到event_base的激活队列中。
并且把这个超时del(删除)掉,这主要是用于非PERSIST 超时event的。
对于PERSIST的event在event_process_active_single_queue又会将其加入到even_base中

4.处理永久超时even
int event_assign()
{
if (events & EV_PERSIST) {
ev->ev_closure = EV_CLOSURE_PERSIST;
} else {
ev->ev_closure = EV_CLOSURE_NONE;
}
}

static int event_process_active_single_queue()
{
for (ev = TAILQ_FIRST(activeq); ev; ev = TAILQ_FIRST(activeq))
{
if (ev->ev_events & EV_PERSIST)
event_queue_remove(base, ev, EVLIST_ACTIVE);
else //不是的话,那么就要把这个event删除掉。
//对于I/O事件是在event_process_active_single_queue删除,但不是在process_time中,对于PRESIST的event没删
event_del_internal(ev);
switch (ev->ev_closure)
case EV_CLOSURE_PERSIST:
event_persist_closure(base, ev);
case EV_CLOSURE_NONE:
//没有设置EV_PERSIST的超时event,就只有一次的监听机会
(*ev->ev_callback)(ev->ev_fd, ev->ev_res, ev->ev_arg);
}
}

static inline void event_persist_closure(struct event_base *base, struct event *ev)
{
//对于超时的具有EV_PERSIST属性的event
if (ev->ev_io_timeout.tv_sec || ev->ev_io_timeout.tv_usec)
event_add_internal(ev, &run_at, 1);
//只具有EV_PERSIST的event就直接调用回调函数
(*ev->ev_callback)(ev->ev_fd, ev->ev_res, ev->ev_arg);//执行回调函数
}

基本时间操作函数
evutil_timeradd(tvp, uvp, vvp)
evutil_timersub(tvp, uvp, vvp)
evutil_timerclear(tvp)
evutil_timercmp(tvp, uvp, cmp)

cache时间
Libevent封装了一个evutil_gettimeofday函数用来获取系统时间,
不过Libevent还是使用了一个cache保存时间,使得更加高效。
static inline void update_time_cache(struct event_base *base)
{
base->tv_cache.tv_sec = 0;
if (!(base->flags & EVENT_BASE_FLAG_NO_CACHE_TIME))
gettime(base, &base->tv_cache);
}
tv_cache是通过调用gettime来获取时间。由于tv_cache.tv_sec已经赋值为0,所以它将使用系统提供的时间函数得到时间。

使用monotonic时间
monotonic时间是boot启动后到现在的时间。用户是不能修改这个时间。
在event_base_new_with_config函数中会调用detect_monotonic函数检测。将全局变量use_monotonic设置为1
static void detect_monotonic(void)
{
if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0)
use_monotonic = 1; //系统支持monotonic时间
}

static int gettime(struct event_base *base, struct timeval *tp)
{
if (base->tv_cache.tv_sec) {
*tp = base->tv_cache;
return (0);
}
if (use_monotonic)
clock_gettime(CLOCK_MONOTONIC, &ts);
else
evutil_gettimeofday(tp, NULL)
}
gettime首先从cache中获取时间,若cache为0,就判断系统是不是支持monotonic,如果也不支持,则只能从evutil_gettimeofday中获取
如果Libevent所在的系统支持monotonic时间,那么根本就不用考虑用户手动修改系统时间这坑爹的事情。

尽可能精确记录时间差
如果知道了用户往回调了多长时间,那么将小根堆中的全部event的时间都往回调一样的时间即可。Libevent调用timeout_correct函数处理这个问题。
static void timeout_correct(struct event_base *base, struct timeval *tv)
{
//如果系统支持monotonic时间,那么就不需要校准时间了
if (use_monotonic)
return;
//tv的时间更大,说明用户没有往回调系统时间。那么不需要处理
if (evutil_timercmp(tv, &base->event_tv, >=)) {
base->event_tv = *tv;
return;
}
evutil_timersub(&base->event_tv, tv, &off);//off差值,即用户调小了多少

for (; size-- > 0; ++pev) {  
    struct timeval *ev_tv = &(**pev).ev_timeout;  
    //前面已经用off保存了,用户调小了多少。现在只需  
    //将小根堆的所有event的超时时间(绝对时间)都减去这个off即可  
    evutil_timersub(ev_tv, &off, ev_tv);  
}  

}

但毕竟Libevent是用户态的库,不能做到用户修改系统时间前的一刻保存系统时间。
所以在event_base_loop函数中的while循环体里面会有gettime(base, &base->event_tv);这是为了能多采点。
这个while循环里面还会执行多路IO复用函数和处理被激活event的回调函数(这个回调函数执行多久也是个未知数)。
这两个函数的执行需要的时间可能会比较长,如果用户刚才是在执行完这两个函数之后修改系统时间,那么event_tv保存的时间就不怎么精确了。

int event_base_loop(struct event_base *base, int flags)
{
while(!done)
{
clear_time_cache(base);
timeout_correct(base, &tv);
//保存系统时间。如果有cache,将保存cache时间。
gettime(base, &base->event_tv);
//之所以要在进入dispatch之前清零,是因为进入
//dispatch后,可能会等待一段时间。cache就没有意义了。
//如果第二个线程此时想add一个event到这个event_base里面,在
//event_add_internal函数中会调用gettime。如果cache不清零,
//那么将会取这个cache时间。这将取一个不准确的时间。
clear_time_cache(base);
//多路IO复用函数
res = evsel->dispatch(base, tv_p);
//将系统时间赋值到cache中
update_time_cache(base);
timeout_process(base);
}
}
在dispatch和event_process_active之间有一个update_time_cache。
而前面的gettime(base,&base->event_tv);实际上取的就是cache的时间。
所以,如果该Libevent支持cache的话,会精确那么一些。
一般来说,用户为event设置的回调函数,不应该执行太久的时间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值