超时事件,在libevent中或许收到了更多的关照,这里使用了两种数据来处理,第一个就是小根堆,第二个就是不同相对超时时间的队列common_timeout。
1.为什么要使用两种不同的数据结构呢?
用于超时管理的min_heap,在执行主循环的过程中,它每次都会去检查min_heap的堆顶event是否超时,如果超时的话就进行相应的处理并且从min_heap中移除这个event,然后调整整个堆,直到堆顶event未超时则停止检查。这样每次删除堆顶超时的event时间复杂度只需要O(logn),假设有m个event超时了需要同时处理,需要花费的时间就是O(mlogn),如果有大量相同的相对超时时间,并且超时时间一致,那么小根堆很多时间都是在调整堆,common_timeout这一结构考虑了这一情况,将相对时间相同的超时event按照超时时间升序连接成队列,只将队首的超时时间放入小根堆中,如果此时超时时间发生了,那么只需要取出小根堆中队首这个元素,直接向后遍历即可,就不需要频繁调整小根堆了。
单纯的小根堆数据结构,就不在这里介绍了,我将超时时间进行讲解。
2.基本数据结构
//event-internal.h
struct common_timeout_list {
/* List of events currently waiting in the queue. */
struct event_list events; //event的双向链表
/* 'magic' timeval used to indicate the duration of events in this
* queue. */
struct timeval duration; //该common_timeout_list的相对超时时长,events双向链表中的所有event都是相同的超时时长
/* Event that triggers whenever one of the events in the queue is
* ready to activate */
struct event timeout_event; //“event代表”,最终只有这个event实际插到了min_heap中
/* The event_base that this timeout list is part of */
struct event_base *base; //该common_timeout_list所在的event_base
};
struct event_base
{
......
/** An array of common_timeout_list* for all of the common timeout
* values we know. */
struct common_timeout_list **common_timeout_queues; //common_timeout_list *数组,存放不同超时时长的common_timeout_list的指针
/** The number of entries used in common_timeout_queues */
int n_common_timeouts; //common_timeout_queues中实际的元素个数
/** The total size of common_timeout_queues. */
int n_common_timeouts_allocated; //common_timeout_queues的容量
......
}
每个base中都有一个common_timeout_list *的数组common_timeout_queues,它其中每个元素都指向一个common_timeout_list,而每个common_timeout_list会将一个单独的timeout_event插入小根堆里,其超时时长就是队首的超时时长。然后当处理这个event的时候,其回调函数的额外参数args就是这个common_timeout_list头指针,因为队列中是按超时时长递增的,从而只需要遍历即可。
3. 设置timeout队列common_timeout
对于一个timeval超时结构体来说,它有两个成员,一个数tv_sec用来指明超时时间中的秒数,一个就是tv_usec用来指明超时时间中的微秒数。由于微秒的数值范围只能是0~999999,而tv_usec的变量类型实际上是32位的,能表示的数值范围远远大于999999,因此用低20位足以来表示timeval中的tv_usec,这样一来,tv_usec的高12位就是没有使用的。而libevent中则是通过这高12位来区分一个timeval超时结构体是common_timeout还是普通的timeout。有如下定义:
//event-internal.h
#define COMMON_TIMEOUT_MICROSECONDS_MASK 0x000fffff //取低20位掩码
//event.c
#define MICROSECONDS_MASK COMMON_TIMEOUT_MICROSECONDS_MASK //取低20位,即微秒超时时长
#define COMMON_TIMEOUT_IDX_MASK 0x0ff00000 //20~27bit为该超时时长在common_timeout_queues中的位置
#define COMMON_TIMEOUT_IDX_SHIFT 20 //微秒最大为999999,因此用低20位存储即可,高12位中前4位标识是否为common_timeout
#define COMMON_TIMEOUT_MASK 0xf0000000 //取高四位掩码
#define COMMON_TIMEOUT_MAGIC 0x50000000 //高四位标志是否为common timeout
对于高12位来说,tv_usec的高4位用来判断一个这个timeval是否是common_timeout,因为用