目录
获取common_timeout在common_timeout_queues中的下标
判断两个timeval是否是同样的common_timeout
获取common_timeout对应的common_timeout_list
以下源码均基于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中实