libevent源码剖析-最小堆实现定时器

 Libevent 源码下载可以去官网  github

 

Libevent使用堆来管理Timer事件,其key值就是事件的超时时间,源代码位于文件minheap-internal.h中。

所有的数据结构书中都有关于堆的详细介绍,向堆中插入、删除元素时间复杂度都是O(lgN),N为堆中元素的个数,而获取最小key值(小根堆)的复杂度为O(1)。堆是一个完全二叉树,基本存储方式是一个数组。

看函数名都挺好懂的,只是下面这个结构体变量命名比较坑....

typedef struct min_heap
{
	struct event** p; // 堆的数组
	size_t n, a;      // 堆元素个数, 堆分配内存大小
} min_heap_t;

      Libevent实现的堆还是比较轻巧的,轻巧到什么地方呢,就以插入元素为例,来对比说明,下面伪代码中的size表示当前堆的元素个数:

典型的代码逻辑如下:

Heap[size++] = new; // 先放到数组末尾,元素个数+1  
// 下面就是shift_up()的代码逻辑,不断的将new向上调整  
_child = size;  
while(_child>0) // 循环  
{  
   _parent = (_child-1)/2; // 计算parent  
   if(Heap[_parent].key < Heap[_child].key)  
      break; // 调整结束,跳出循环  
   swap(_parent, _child); // 交换parent和child  
}  

        而libevent的heap代码对这一过程做了优化,在插入新元素时,只是为新元素预留了一个位置hole(初始时hole位于数组尾部),但并不立刻 将新元素插入到hole上,而是不断向上调整hole的值,将父节点向下调整,最后确认hole就是新元素的所在位置时,才会真正的将新元素插入到 hole上,因此在调整过程中就比上面的代码少了一次赋值的操作, 下面就是min_heap_shift_up_()的源代码逻辑,不断的将新的“预留位置”向上调整:

void min_heap_shift_up_(min_heap_t* s, size_t hole_index, struct event* e)
{
    size_t parent = (hole_index - 1) / 2;
	//如果hole_index不等于0且父节点元素大于所给的元素,继续比较,直到到达hole_index为根元素,
    //或者现在的父元素大于了e,找到插入的位置
    while (hole_index && min_heap_elem_greater(s->p[parent], e))
    {
        //父节点元素值大,将父节点放到现在的hole_index上的位置
	(s->p[hole_index] = s->p[parent])->ev_timeout_pos.min_heap_idx = hole_index;
		
	//hole_index赋值为父节点的索引
	hole_index = parent;
		
	//找到现在的hole_index的父节点索引
	parent = (hole_index - 1) / 2;
    }
	
	//跳出循环找到了要插入的位置,位置的索引就是现在的hole_index
    (s->p[hole_index] = e)->ev_timeout_pos.min_heap_idx = hole_index;
}

        由于每次调整都少做一次赋值操作,在调整路径比较长时,调整效率会比第一种有所提高。libevent中的min_heap_shift_up_()函数就是上面逻辑的具体实现,对应的向下调整函数是min_heap_shift_down_()。

void min_heap_shift_down_(min_heap_t* s, size_t hole_index, struct event* e)
{
    size_t min_child = 2 * (hole_index + 1);  //右孩子索引
	
	//存在右孩子,如果不存在右子树,直接向下调整,因为最多存在左子树,且值肯定不小于父节点,可以直接向下调整
    while (min_child <= s->n)
    {
	//选择左右孩子值最小的孩子的索引,根据优先级可以加()进行更好的查看
	min_child -= min_child == s->n || min_heap_elem_greater(s->p[min_child], s->p[min_child - 1]);
	//如果e元素不大于最小的孩子元素,没有必要再继续,hole_index就是他的位置
	if (!(min_heap_elem_greater(e, s->p[min_child])))
	    break;
	//将小的孩子元素放到hole_index位置上
	(s->p[hole_index] = s->p[min_child])->ev_timeout_pos.min_heap_idx = hole_index;
	 //hole_index保存当前小的孩子索引
	hole_index = min_child;
	//当前小的孩子位置空出,继续下一次循环,比较当前小的孩子的左右孩子
	min_child = 2 * (hole_index + 1);
    }
	
	
    (s->p[hole_index] = e)->ev_timeout_pos.min_heap_idx = hole_index;
}

 

你可能感兴趣的:(libevent)