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下json库的编译及例程

    .下载JsonCpp http://sourceforge.net/projects/jsoncpp/files/ .下载scons http://sourceforge.net/projects/s ...

  2. ARP表 MAC表 路由表

    ARP表是一个动态表,存储在计算机当中,目的是做一个ip地址与mac地址的对应.假设在同一子网段,计算机A与计算机B通信计算机A的ip地址192.168.0.1 MAC地址AA-AA-AA-AA-AA ...

  3. (转)java中查找List的快捷小方法

    相信java开发的程序员,经常会遍历list里的数据,来查找想要的数据.之后选出来在做处理,我这有个小方法在大量数据的情况下能更快捷,当然这方法只对菜鸟有点用,对老鸟来说也许有更好的方法,请指点 遍历 ...

  4. Toolbar使用

    原文地址 http://www.cnblogs.com/Dentist/p/4370176.html Android4.0出现的Actionbar提供了同意方便的导航管理.很大程度的统一了Androi ...

  5. jenkin执行权限问题

    jenkins执行报错 stderr: Permission denied, please try again. Permission denied, please try again. Permis ...

  6. EMI (电磁干扰)

    EMI是指电子产品工作会对周边的其他电子产品造成干扰,与此关联的还有EMC规范.是电子电器产品经常遇上的问题.干扰种类有传导干扰和辐射干扰. 电磁干扰(Electromagnetic Interfer ...

  7. 分布式缓存系统 Memcached CAS协议

    Memcached在1.2.4版本后新增了CAS(Check and Set)协议,主要用于并发控制:memcached中同一个item同时被多个线程(多个客户端)更改的并发问题.CAS协议最本质的东 ...

  8. spring-session之一:简介、使用及实现原理

    一.背景 http session(企业)一直都是我们做集群时需要解决的一个难题,我们知道HttpSession是通过Servlet容器创建和管理的,像Tomcat/Jetty都是保存在内存中的.而如 ...

  9. Java-Runoob:Java 条件语句

    ylbtech-Java-Runoob:Java 条件语句 1.返回顶部 1. Java 条件语句 - if...else 一个 if 语句包含一个布尔表达式和一条或多条语句. 语法 if 语句的语法 ...

  10. 记一次oralce 11g r2 rac安装问题

    环境:虚拟机[root@rac1 ~]# lsb_release -aLSB Version:        :base-4.0-amd64:base-4.0-noarch:core-4.0-amd6 ...