libevent源码阅读笔记——时间管理

event_base对时间的管理

libevent 中时间采用struct timeval作为时间类型,因为timeval在很多平台都支持,比如Windows、Linux。libevent接口传入到都是超时时间间隔,所以程序内部必须做转换。 绝对时间=当前时间+超时时间间隔。正常情况下,系统的时间是一直递增的,所以计算后的绝对时间>当前时间。但是,用户的行为是永远难以捉摸的。如果用户在event_add后,timeout_process前修改了系统时间,那么在处理Timer事件时就会异常。下文将会讲到。
struct event_base {
	// ... ...
	/**存储通用时间队列 */
	struct common_timeout_list **common_timeout_queues;
	/** The number of entries used in common_timeout_queues */
	int n_common_timeouts;
	/**通用时间队列中元素个数 */
	int n_common_timeouts_allocated;
	// ... ...
	/** Stored timeval; used to detect when time is running backwards. */
	struct timeval event_tv;

	/** 管理超时事件的小根堆*/
	struct min_heap timeheap;

	/** 时间缓存,避免程序内部多次调用gettimeofday操作,引发的上下文切换 */
	struct timeval tv_cache;

#if defined(_EVENT_HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
	/** Difference between internal time (maybe from clock_gettime) and
	 * gettimeofday. */
	struct timeval tv_clock_diff;
	/** Second in which we last updated tv_clock_diff, in monotonic time. */
	time_t last_updated_clock_diff;
#endif

	//... ...
};

libevent对Timer的三种管理方式:

libevent封装了一个获取当前时间的函数, gettime(struct event_base *base, struct timeval *tp)。

static int
gettime(struct event_base *base, struct timeval *tp)
{
	EVENT_BASE_ASSERT_LOCKED(base);

	if (base->tv_cache.tv_sec) {
		*tp = base->tv_cache;
		return (0);
	}

#if defined(_EVENT_HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
	if (use_monotonic) {
		struct timespec	ts;

		if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1)
			return (-1);

		tp->tv_sec = ts.tv_sec;
		tp->tv_usec = ts.tv_nsec / 1000;
		//1s 更新一次
		if (base->last_updated_clock_diff + CLOCK_SYNC_INTERVAL
		    < ts.tv_sec) {
			struct timeval tv;
			evutil_gettimeofday(&tv,NULL);
			evutil_timersub(&tv, tp, &base->tv_clock_diff);
			base->last_updated_clock_diff = ts.tv_sec;
		}

		return (0);
	}
#endif

	return (evutil_gettimeofday(tp, NULL));
}

在gettime函数中,优先获取tv_cache的时间,如果缓存时间为0,才去调用系统函数获取时间。


1. Cache Time
struct timeval tv_cache,用于缓存当前程序获取到的系统时间,为了避免多次调用gettimeofday系统函数,造成不必要的性能消耗。

<span style="font-family: Arial, Helvetica, sans-serif;">
</span>
<span style="font-family: Arial, Helvetica, sans-serif;">static inline void</span>
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);
}

update_time_cache 函数将调用gettime更新缓存中的时间。对超时的处理主要集中在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, retval = 0;

	/* Grab the lock.  We will release it inside evsel.dispatch, and again
	 * as we invoke user callbacks. */
	EVBASE_ACQUIRE_LOCK(base, th_base_lock);

	if (base->running_loop) {
		event_warnx("%s: reentrant invocation.  Only one event_base_loop"
		    " can run on each event_base at once.", __func__);
		EVBASE_RELEASE_LOCK(base, th_base_lock);
		return -1;
	}

	base->running_loop = 1;
<span style="white-space:pre">	</span>//清空缓存时间,这么做是为了保证每次循环开始时,缓存时间是最新的
<span style="white-space:pre">	</span>//因为只有缓存时间为0,才会调用系统函数去更新缓存时间
	clear_time_cache(base);

	if (base->sig.ev_signal_added && base->sig.ev_n_signals_added)
		evsig_set_base(base);

	done = 0;

#ifndef _EVENT_DISABLE_THREAD_SUPPORT
	base->th_owner_id = EVTHREAD_GET_ID();
#endif

	base->event_gotterm = base->event_break = 0;

	while (!done) {
		base->event_continue = 0;

		/* Terminate the loop if we have been asked to */
		if (base->event_gotterm) {
			break;
		}

		if (base->event_break) {
			break;
		}
<span style="white-space:pre">		</span>//进行时间校准,如果用户修改了系统时间,需要重新处理超时的绝对时间
		timeout_correct(base, &tv);

		tv_p = &tv;
		if (!N_ACTIVE_CALLBACKS(base) && !(flags & EVLOOP_NONBLOCK)) {
			timeout_next(base, &tv_p);
		} else {
			/*
			 * 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) && !N_ACTIVE_CALLBACKS(base)) {
			event_debug(("%s: no events registered.", __func__));
			retval = 1;
			goto done;
		}

		//更新event_tv,event_tv需要定期的更新。因为time_correct将用event_tv作为用户修改前的系统时间标准,
<span style="white-space:pre">		</span>//用来计算修改的差值。定期更新是为了防止用户在修改完时间后又多次进行修改。这样可以尽可能保证准确。
		gettime(base, &base->event_tv);
<span style="white-space:pre">		</span>//这里需要再次清空缓存时间,因为在dispatch函数中,event可能会wait一段时间,这样缓存中的时间就不准了,
<span style="white-space:pre">		</span>//如果在dispatch的时候又event_add,在调用gettime获取的时间非正常时间。
		clear_time_cache(base);
<span style="white-space:pre">		</span>//多路IO复用函数。
		res = evsel->dispatch(base, tv_p);

		if (res == -1) {
			event_debug(("%s: dispatch returned unsuccessfully.",
				__func__));
			retval = -1;
			goto done;
		}
<span style="white-space:pre">		</span>//更新缓存时间
		update_time_cache(base);
<span style="white-space:pre">		</span>//处理超时event
		timeout_process(base);

		if (N_ACTIVE_CALLBACKS(base)) {
			int n = event_process_active(base);
			if ((flags & EVLOOP_ONCE)
			    && N_ACTIVE_CALLBACKS(base) == 0
			    && n != 0)
				done = 1;
		} else if (flags & EVLOOP_NONBLOCK)
			done = 1;
	}
	event_debug(("%s: asked to terminate loop.", __func__));

done:
	clear_time_cache(base);
	base->running_loop = 0;

	EVBASE_RELEASE_LOCK(base, th_base_lock);

	return (retval);
}

timeout_process 函数用于激活超时event

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);
<span style="white-space:pre">	</span>//从小根堆依次取最小的超时时间,如果未超时则退出循环,否则,调用event_del_internal,将event
<span style="white-space:pre">	</span>//从对应的队列中删除,然后调用event_active_nolock激活event
	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));
<span style="white-space:pre">		</span>//EV_TIMEOUT表示改event的回调为超时回调,1表示将有1个回调被调用。
		event_active_nolock(ev, EV_TIMEOUT, 1);
	}
}

2.CLOCK_MONOTONIC

前面提到的,如果用户在event未被触发前修改了系统时间,那么就会导致gettimeofday获取时间错误。为了解决该问题,libevent使用了CLOCK_MONOTONIC属性。CLOCK_MONOTONIC获取到的是系统启动到现在所走过的时间,这个时间用户无法修改。在支持CLOCK_MONOTONIC的系统上运行,只需要clock_gettime(CLOCK_MONOTONIC, &ts),便可以获取到一个单调递增的time。

 

CLOCK_MONOTONIC处理系统时间跳变很方便,但是不是所有的系统都支持。所以libevent提供了自己处理系统时间跳变的方法。当用户改变了系统时间后,可以将已经存放在小根堆里的所有Time都减去相同的time difference。因为小根堆里存放的都是每个Timer event超时的absolutely time,所以这就相当于把minheap里的所有Timer也随着系统时间一起做了调整。对每个元素进行相同的时间调整本身不会改变minheap的结构,只需要遍历每个元素即可。

static void
timeout_correct(struct event_base *base, struct timeval *tv)
{
	/* Caller must hold th_base_lock. */
	struct event **pev;
	unsigned int size;
	struct timeval off;
	int i;

	if (use_monotonic)
		return;

	/* Check if time is running backwards */
	gettime(base, tv);
<span style="white-space:pre">	</span>//检测是否系统时间被往回调整
	if (evutil_timercmp(tv, &base->event_tv, >=)) {
		base->event_tv = *tv;
		return;
	}

	event_debug(("%s: time is running backwards, corrected",
		    __func__));
<span style="white-space:pre">	</span>//计算出往回调整的时间差off
	evutil_timersub(&base->event_tv, tv, &off);

<span style="white-space:pre">	</span>//对于小根堆,因为对每个元素都做相同的改变,不会影响堆结构,只需遍历
	pev = base->timeheap.p;
	size = base->timeheap.n;
	for (; size-- > 0; ++pev) {
		struct timeval *ev_tv = &(**pev).ev_timeout;
		evutil_timersub(ev_tv, &off, ev_tv);
	}
<span style="white-space:pre">	</span>//对于通用时间队列,也只需要遍历调整
	for (i=0; i<base->n_common_timeouts; ++i) {
		struct event *ev;
		struct common_timeout_list *ctl =
		    base->common_timeout_queues[i];
		TAILQ_FOREACH(ev, &ctl->events,
		    ev_timeout_pos.ev_next_with_common_timeout) {
			struct timeval *ev_tv = &ev->ev_timeout;
			ev_tv->tv_usec &= MICROSECONDS_MASK;
			evutil_timersub(ev_tv, &off, ev_tv);
			ev_tv->tv_usec |= COMMON_TIMEOUT_MAGIC |
			    (i<<COMMON_TIMEOUT_IDX_SHIFT);
		}
	}

	//同时,讲调整后的当前时间赋值event_tv,以防用户多次修改时间
	base->event_tv = *tv;
}

注意:

个人觉得libevent对时间调整的处理比较全面,但是却存在一个问题。 libevent只对时间往前调整做了校准,没有对时间往后调整做处理!如果用户在event没有触发前,向后修改系统时间,可能会导致许多事件被同时提前激活。不过我认为,既然用户下发了超时事件将在10:00触发,但是在9:40分的时候却又调整系统时间到10:10。用户这种行为本身就等于主动的提前去触发事件了。libevent开发者也是这么想的,所以才没有对往后调整时间的情况做处理吧。 微笑

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值