libevent源码深度剖析九

——集成定时器事件

张亮

现在再来详细分析libevent中I/O事件和Timer事件的集成,与Signal相比,Timer事件的集成会直观和简单很多。Libevent对堆的调整操作做了一些优化,本节还会描述这些优化方法。

1 集成到事件主循环

因为系统的I/O机制像select()和epoll_wait()都允许程序制定一个最大等待时间(也称为最大超时时间)timeout,即使没有I/O事件发生,它们也保证能在timeout时间内返回。

那么根据所有Timer事件的最小超时时间来设置系统I/O的timeout时间;当系统I/O返回时,再激活所有就绪的Timer事件就可以了,这样就能将Timer事件完美的融合到系统的I/O机制中了。

具体的代码在源文件event.c的event_base_loop()中,现在就对比代码来看看这一处理方法:

  1. if (!base->event_count_active && !(flags & EVLOOP_NONBLOCK)) {
  2. // 根据Timer事件计算evsel->dispatch的最大等待时间
  3. timeout_next(base, &tv_p);
  4. } else {
  5. // 如果还有活动事件,就不要等待,让evsel->dispatch立即返回
  6. evutil_timerclear(&tv);
  7. }
  8. // ...
  9. // 调用select() or epoll_wait() 等待就绪I/O事件
  10. res = evsel->dispatch(base, evbase, tv_p);
  11. // ...
  12. // 处理超时事件,将超时事件插入到激活链表中
  13. timeout_process(base);

timeout_next()函数根据堆中具有最小超时值的事件和当前时间来计算等待时间,下面看看代码:

  1. static int timeout_next(struct event_base *base, struct timeval **tv_p)
  2. {
  3. struct timeval now;
  4. struct event *ev;
  5. struct timeval *tv = *tv_p;
  6. // 堆的首元素具有最小的超时值
  7. if ((ev = min_heap_top(&base->timeheap)) == NULL) {
  8. // 如果没有定时事件,将等待时间设置为NULL,表示一直阻塞直到有I/O事件发生
  9. *tv_p = NULL;
  10. return (0);
  11. }
  12. // 取得当前时间
  13. gettime(base, &now);
  14. // 如果超时时间<=当前值,不能等待,需要立即返回
  15. if (evutil_timercmp(&ev->ev_timeout, &now, <=)) {
  16. evutil_timerclear(tv);
  17. return (0);
  18. }
  19. // 计算等待的时间=当前时间-最小的超时时间
  20. evutil_timersub(&ev->ev_timeout, &now, tv);
  21. return (0);
  22. }

2 Timer小根堆

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

所有的数据结构书中都有关于堆的详细介绍,向堆中插入、删除元素时间复杂度都是O(lgN),N为堆中元素的个数,而获取最小key值(小根堆)的复杂度为O(1)。堆是一个完全二叉树,基本存储方式是一个数组。
     
Libevent实现的堆还是比较轻巧的,虽然我不喜欢这种编码方式(搞一些复杂的表达式)。轻巧到什么地方呢,就以插入元素为例,来对比说明,下面伪代码中的size表示当前堆的元素个数:

典型的代码逻辑如下:

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

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

下面就是shift_up()的代码逻辑,不断的将new的“预留位置”向上调整

  1. // 下面就是shift_up()的代码逻辑,不断的将new的“预留位置”向上调整
  2. _hole = size; // _hole就是为new预留的位置,但并不立刻将new放上
  3. while(_hole>0) // 循环
  4. {
  5. _parent = (_hole-1)/2; // 计算parent
  6. if(Heap[_parent].key < new.key)
  7. break; // 调整结束,跳出循环
  8. Heap[_hole] = Heap[_parent]; // 将parent向下调整
  9. _hole = _parent; // 将_hole调整到_parent
  10. }
  11. Heap[_hole] = new; // 调整结束,将new插入到_hole指示的位置
  12. size++; // 元素个数+1

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

举个例子,向一个小根堆3, 5, 8, 7, 12中插入新元素2,使用第一中典型的代码逻辑,其调整过程如下图所示:

使用libevent中的堆调整逻辑,调整过程如下图所示:

对于删除和元素修改操作,也遵从相同的逻辑,就不再罗嗦了。

3 小节

通过设置系统I/O机制的wait时间,从而简捷的集成Timer事件;主要分析了libevent对堆调整操作的优化。

libevent源码深度剖析九的更多相关文章

  1. libevent 源码深度剖析十三

    libevent 源码深度剖析十三 —— libevent 信号处理注意点 前面讲到了 libevent 实现多线程的方法,然而在多线程的环境中注册信号事件,还是有一些情况需要小心处理,那就是不能在多 ...

  2. libevent源码深度剖析十二

    libevent源码深度剖析十二 ——让libevent支持多线程 张亮 Libevent本身不是多线程安全的,在多核的时代,如何能充分利用CPU的能力呢,这一节来说说如何在多线程环境中使用libev ...

  3. libevent源码深度剖析十一

    libevent源码深度剖析十一 ——时间管理 张亮 为了支持定时器,Libevent必须和系统时间打交道,这一部分的内容也比较简单,主要涉及到时间的加减辅助函数.时间缓存.时间校正和定时器堆的时间值 ...

  4. libevent源码深度剖析十

    libevent源码深度剖析十 ——支持I/O多路复用技术 张亮 Libevent的核心是事件驱动.同步非阻塞,为了达到这一目标,必须采用系统提供的I/O多路复用技术,而这些在Windows.Linu ...

  5. libevent源码深度剖析八

    libevent源码深度剖析八 ——集成信号处理 张亮 现在我们已经了解了libevent的基本框架:事件管理框架和事件主循环.上节提到了libevent中I/O事件和Signal以及Timer事件的 ...

  6. libevent源码深度剖析七

    libevent源码深度剖析七 ——事件主循环 张亮 现在我们已经初步了解了libevent的Reactor组件——event_base和事件管理框架,接下来就是libevent事件处理的中心部分 — ...

  7. libevent源码深度剖析六

    libevent源码深度剖析六 ——初见事件处理框架 张亮 前面已经对libevent的事件处理框架和event结构体做了描述,现在是时候剖析libevent对事件的详细处理流程了,本节将分析 lib ...

  8. libevent源码深度剖析五

    libevent源码深度剖析五 ——libevent的核心:事件event 张亮 对事件处理流程有了高层的认识后,本节将详细介绍libevent的核心结构event,以及libevent对event的 ...

  9. libevent源码深度剖析四

    libevent源码深度剖析四 ——libevent源代码文件组织 1 前言 详细分析源代码之前,如果能对其代码文件的基本结构有个大概的认识和分类,对于代码的分析将是大有裨益的.本节内容不多,我想并不 ...

随机推荐

  1. linux TCP Fast Open开启和测试

    linux上要开启TCP Fast Open,内核版本至少为3.7.0, 且需要设置 /proc/sys/net/ipv4/tcp_fastopen 为3. 开启后,如果有连接进来,使用如下命令查看: ...

  2. ES中保护对象的措施总结

    必要性:  JS中的对象可随意修改属性值,可随意添加删除属性,太乱,数据安全得不到保障. 如何保护: 保护属性: 保护对属性值的修改 对象属性分为: 命名属性: 可直接用.访问到的属性 数据属性: 直 ...

  3. RAD Studio Mobile Roadmap updated,XE5 will released on next month, Andriod will be supported.

    RAD Studio Mobile Roadmap updated   Embarcadero updated his RAD Studio Mobile Roadmap. This concern ...

  4. Unity3D开发之Matrix4x4矩阵变换

    在Unity开发中时常会用到Matrix4x4矩阵来变换场景中对象的位置.旋转和缩放.但是很多人都不太理解这儿Matrix4x4变换矩阵.通过DX中的变换矩阵我来讲一讲在unity中这个变换矩阵是怎么 ...

  5. xcode加载静态链接库.a文件总是失败

    明明项目是对的,代码没有问题,并且把项目作为库项目引入到新项目中没问题,可是一旦把项目编译出.a文件,引入到新项目中不知为何会有几率出现一大堆错误,其实是xcode的缓存机制在作怪,去这个目录: /U ...

  6. Django之mysql表单操作

    在Django之ORM模型中总结过django下mysql表的创建操作,接下来总结mysql表记录操作,包括表记录的增.删.改.查. 1. 添加表记录 class UserInfo(models.Mo ...

  7. HTTP Status 500 - Error instantiating servlet class XXXX

    问题描述 web项目中请求出现错误,如下:  HTTP Status 500 - Error instantiating servlet class XXXX类  type Exception rep ...

  8. CodeForces - 484BMaximum Value(hash优化)

    个人心得:周测题目,一题没出,难受得一批.这个题目做了一个半小时还是无限WR,虽然考虑到了二分答案这个点上面了, 奈何二分比较差就想用自己的优化,虽然卡在了a=k*b+c,这里但是后面结束了这样解决还 ...

  9. webpack新版本4.12应用九(配置文件之输出(output))

    output 位于对象最顶级键(key),包括了一组选项,指示 webpack 如何去输出.以及在哪里输出你的「bundle.asset 和其他你所打包或使用 webpack 载入的任何内容」. ou ...

  10. 常用DNS列表(电信、网通)

    电信 DNS 列表 -- 共 32 条 (按拼音排序) 电信 A安徽 202.102.192.68 202.102.199.68     电信 A澳门 202.175.3.8 202.175.3.3 ...