libevent源码学习(12):超时管理之common_timeout

libevent的common_timeout机制用于优化大量超时event的管理,通过将相同超时时长的event归类,减少处理超时event的时间复杂度。本文详细介绍了common_timeout的作用、结构定义、与普通timeout的区别,以及如何创建、添加和激活。通过common_timeout,可以提高处理多个超时event的效率,但是否使用取决于具体场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

前言

common_timeout的作用

common_timeout的结构定义

common_timeout与一般timeout的区分

获取common_timeout在common_timeout_queues中的下标

判断一个timeval是否为common_timeout

判断两个timeval是否是同样的common_timeout

获取common_timeout对应的common_timeout_list

创建一个common_timeout

为event添加common_timeout

从common_timeout_list到min_heap

激活common_timeout对应的event

总结


以下源码均基于libevent-2.0.21-stable。

前言

       用于超时管理的min_heap,在执行主循环的过程中,它每次都会去检查min_heap的堆顶event是否超时,如果超时的话就进行相应的处理并且从min_heap中移除这个event,然后调整整个堆,直到堆顶event未超时则停止检查。这种方法虽然好,逻辑清晰,看上去每次删除堆顶超时的event时间复杂度只需要O(logn),效率也足够高,但是如果某次主循环中超时的event过多,假设有m个event超时了需要同时处理,那么此时需要花费的时间就是O(mlogn),当m足够多的时候,这个效率还是比较低的,因此就引入了common_timeout这一结构。

       那么它的作用是什么呢?

common_timeout的作用

       简单来说,common_timeout把base中所有拥有共同点的event放在了一起,而这个所谓的“共同点”就是指超时时长相同,这些超时时长相同的event,他们的超时时间是不同的。

       举个例子,我添加了一个eventA,设置它的超时时长为5分钟,即如果5分钟内没有触发相应事件,那么5分钟后就直接进行回调处理;然后我再添加了一个eventB,也设置它的超时时长为5分钟。那么就称eventA和eventB具有相同的超时时长。如果eventA添加的时间为10:00,eventB添加的时间为11:00,那么二者的超时时间就一个是10:05,另一个就是11:05,因此超时时长相同,但是超时时间是不同的。

       拥有相同超时时长的所有event构成一个链表events,并且让它们按照超时时间的先后按升序排列(即相同超时时长中最先超时的那个event放在最前面),而events中设置一个内部使用的timeout_event作为代表,把最先超时的那个event的超时时间添加到timeout_event中,然后把timeout_event放到min_heap中,当放到min_heap中的timeout_event超时,就回到events中,从前往后把所有超时的event全部激活。下面来分析这种情况下的时间复杂度。

       在这种情况下,相当于每一个由相同超时时长的event组成的链表都在min_heap中存在一个“代表”,因此如果有t个链表的“代表”在min_heap中超时,那么处理这个"代表"后调整堆花费的时间就是O(tlogn),如果一共有m个event超时了,相当于所有链表加起来需要遍历m个event,因此common_timeout在处理m个event超时的时间复杂度就是O(tlogn+m),由此也能看出来使用common_timeout+min_heap和只使用min_heap的差别了:如果t远小于m,相当于超时的event分布的链表比较集中,那么前者的效率更高,这种优势当n越大时越明显;如果t和m相近,相当于超时的event分布在不同的链表,此时还是后者效率更高。

       因此到底是否使用common_timeout,还是视情况而定,这也是为什么在libevent中虽然设计了common_timeout,但是并没有将其直接用来管理超时,而是留给用户接口去选择是否使用common_timeout,可见,common_timeout+min_heap的超时管理并非就一定比只使用min_heap的效率高。而至于具体在什么情况下使用哪种方式,个人觉得如果超时的event很多那还是应该考虑使用common_timeout+min_heap,因为event很多的话分布的链表也更大概率密集一些;如果超时的event比较少的话,还是应该只使用min_heap。

common_timeout的结构定义

       在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的容量
    ......
}

         在event_base中,common_timeout_queues是一个common_timeout_list *类型的指针数组,其中每个元素都指向一个common_timeout_list,common_timeout_list的定义如下:

//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
};

//event_struct.h

TAILQ_HEAD (event_list, event);  //由event组成的双向链表

        从上述二者的定义可以知道,每一个event_base都对应一个common_timeout_list *的数组common_timeout_queues,它其中每个元素都指向一个common_timeout_List,每一个common_timeout_list都指明了其对应的超时时长(duration),以及超时时长相同都等于duration的所有event组成的双向链表(events)。此外就是timeout_event,这个event最终会作为“代表”插入到min_heap中,实际min_heap处理的超时event也是timeout_event。

        举个例子,假如common_timeout_queues[0]对应的common_timeout_list的duration为3s,那么这个common_timeout_list的events双向链表中就会存放所有超时时长为3s的event。不过这必须保证这里设置的“3s”必须是common_timeout的3s而不是普通超时结构体的3s。

       那么,如何来区分一个超时结构体是common_timeout还是普通的超时结构体呢?

common_timeout与一般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

        这里定义了6个宏定义,其中前两个是相同的。timeval结构体中的tv_usec由32位表示,而实际上微秒的数值只需低20位即可表示,因此,

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值