转自:http://m.blog.csdn.NET/blog/weiqubo/16355653

libev是Marc Lehmann用C写的高性能事件循环库。通过libev,可以灵活地把各种事件组织管理起来,如:时钟、io、信号等。libev在业界内也是广受好评,不少项目都采用它来做底层的事件循环。Node.js也是其中之一。 学习和分析libev库,有助于理解node.js底层的工作原理,同时也可以学习和借鉴libev的设计思想。本文是最近在学习libev源码的一些心得总结吧。

libev示例

先上一个例子,看看libev是怎么使用的吧。

  1. // a single header file is required
  2. #include <ev.h>
  3. #include <stdio.h> // for puts
  4. // every watcher type has its own typedef'd struct
  5. // with the name ev_TYPE
  6. ev_io stdin_watcher;
  7. ev_timer timeout_watcher;
  8. // all watcher callbacks have a similar signature
  9. // this callback is called when data is readable on stdin
  10. static void
  11. stdin_cb (EV_P_ ev_io *w, int revents)
  12. {
  13. puts ("stdin ready");
  14. // for one-shot events, one must manually stop the watcher
  15. // with its corresponding stop function.
  16. ev_io_stop (EV_A_ w);
  17. // this causes all nested ev_run's to stop iterating
  18. ev_break (EV_A_ EVBREAK_ALL);
  19. }
  20. // another callback, this time for a time-out
  21. static void
  22. timeout_cb (EV_P_ ev_timer *w, int revents)
  23. {
  24. puts ("timeout");
  25. // this causes the innermost ev_run to stop iterating
  26. ev_break (EV_A_ EVBREAK_ONE);
  27. }
  28. int
  29. main (void)
  30. {
  31. // use the default event loop unless you have special needs
  32. struct ev_loop *loop = EV_DEFAULT;
  33. // initialise an io watcher, then start it
  34. // this one will watch for stdin to become readable
  35. ev_io_init (&stdin_watcher, stdin_cb, /*STDIN_FILENO*/ 0, EV_READ);
  36. ev_io_start (loop, &stdin_watcher);
  37. // initialise a timer watcher, then start it
  38. // simple non-repeating 5.5 second timeout
  39. ev_timer_init (&timeout_watcher, timeout_cb, 5.5, 0.);
  40. ev_timer_start (loop, &timeout_watcher);
  41. // now wait for events to arrive
  42. ev_run (loop, 0);
  43. // break was called, so exit
  44. return 0;
  45. }

这是libev官网文档的例子,其中libev的使用步骤还是比较清晰的。从main开始入手,可以发现代码中主要做了这么几件事情:

  • 获取ev_loop实例。ev_loop,从名字上可以看出,它代表了一个事件循环,也是我们后面代码的主要组织者。

  • 创建和初始化watcher。libev中定义了一系列的watcher,每类watcher负责一类特定的事件。一般可以通过ev_TYPE_init函数来创建一个watcher实例(TYPE是某一种watcher类型,如:io, timer等)。例子中分别创建了io和timer两个watcher,并绑定了相应的回调函数。当感兴趣的事件发生后,对应的回调函数将会被调用。

  • 将watcher注册到ev_loop中。一般可以通过ev_TYPE_start函数来完成。注册成功后,watcher便和loop关联起来了,当loop中检测到感兴趣的事件发生,便会通知相关的watcher。

  • 启动事件循环。 即后面的ev_run函数。事件循环启动后,当前线程/进程将会被阻塞,直到循环被终止。

在上面的例子中,在两个回调函数中的ev_break函数就是终止循环的地方。当5.5秒超时或是标准输入有输入事件,则会进入到相应的回调函数,然后会终止事件循环,退出程序。

libev工作原理

总的来看,libev其实是实现了Reactor模式。当中主要包含了这么几个角色:watcher, ev_loop和ev_run。

watcher

watcher是Reactor中的Event Handler。一方面,它向事件循环提供了统一的调用接口(按类型区分);另一方面,它是外部代码的注入口,维护着具体的watcher信息,如:绑定的回调函数,watcher的优先级,是否激活等。

在ev.h中我们可以看到各种watcher的定义,如:ev_io, ev_timer等。其中,watcher的公共属性定义如下:

  1. /* shared by all watchers */
  2. #define EV_WATCHER(type)         \
  3. int active; /* private */           \
  4. int pending; /* private */          \
  5. EV_DECL_PRIORITY /* private  int priority; */       \
  6. EV_COMMON /* rw  void *data; */             \
  7. EV_CB_DECLARE (type) /* private */

其中的宏定义如下:

  1. # define EV_DECL_PRIORITY int priority;
  2. # define EV_COMMON void *data;
  3. # define EV_CB_DECLARE(type) void (*cb)(EV_P_ struct type *w, int revents);
  • active: 表示当前watcher是否被激活。ev_TYPE_start调用后置位,ev_TYPE_stop调用后复位;

  • pending: 表示当前watcher有事件就绪,等待处理。pending的值其实就是当前watcher在pendings队列中的下标;

  • priority: 是当前watcher的优先级;

  • data: 附加数据指针,用来在watcher中携带额外所需的数据;

  • cb:是事件触发后的回调函数定义。

具体的watcher再在此基础上添加额外的属性。 开发者可以根据需要,选择特定类型的watcher,创建实例并进行初始化,然后将实例注册到loop中即可。libev中定义了若干种类型的watcher,每类watcher负责解决某一特定领域的问题(如:io, timer, signal等),可以在ev.h中看到这些watcher的定义。

ev_loop

ev_loop则是一个Reactor的角色,是事件循环的上下文环境,就像一根竹签,把前面的watcher实例像糖葫芦一样串起来。

ev_loop的定义

ev_loop的定义在ev.c中,具体如下:

  1. struct ev_loop
  2. {
  3. ev_tstamp ev_rt_now;
  4. #define ev_rt_now ((loop)->ev_rt_now)
  5. #define VAR(name,decl) decl;
  6. #include "ev_vars.h"
  7. #undef VAR
  8. };

ev_vars.h中定义了ev_loop的各种属性。在ev_wrap.h中则定义了与loop相关的各种宏,代码中大多都是以宏的形式进行操作。

watcher的管理

在ev_loop中,watcher按各自的类型进行分类存储。如:io的loop->anfds,timer的loop->timers。ev_TYPE_start在激活watcher后,便将它加入到相应的存储结构中(具体的实现在后面介绍watcher的文章再分析)。

在事件循环中,有事件就绪的watcher会被挑拣出来,保存到ev_loop中。这些就绪的watcher主要由loop->pendings和loop->pendingcnt来维护(如下图所示)。这两个东西都是二维数组,第一维都是优先级。pendings中存的是ANPENDING实例,后者的做要作用就是维护就绪的watcher指针; 而pendingcnt中存的则是对应优先级上的pendings元素的数量。在每个就绪的watcher上也会有一个pending字段记录它在pendings列表中的下标,这样就可以通过watcher很方便的找到它在pendings列表中的位置了,这对删除操作很有帮助。

在一轮事件循环结束后,则会根据优先级,依次触发就绪的watcher。

bool ev_run(loop, flag)

ev_run函数是执行事件循环的引擎,即Reactor模式中的select方法。通过向ev_run函数传递一个ev_loop实例,便可以开启一个事件循环。ev_run实际上是一个巨大的do-while循环,期间会检查loop中注册的各种watcher的事件。如果有事件就绪,则触发相应的watcher。这个循环会一直持续到ev_break被调用或者无active的watcher为止。当然,也可以通过传递EVRUN_NOWAIT或EVRUN_ONCE等flag来控制循环的阻塞行为。

ev_run的工作内容,在官方文档的API中有详细说明,通过文档有助于对ev_run的理解。具体的代码有点长,在这里就不贴了,感兴趣的同学可以在ev.c中查看ev_run的实现代码。剔除掉条件检查和一些无关紧要的代码,主要的流程如下图所示。

可以看到,ev_run的主要工作就是按watcher的分类,先后检查各种类型的watcher上的事件,通过ev_feed_event函数将就绪的watcher加入到pending的数据结构中。最后调用ev_invoke_pending,触发pending中的watcher。完了以后会检查,是否还有active的watcher以及是否有ev_break调用过,然后决定是否要继续下一轮循环。

总结

总的来看,libev的结构设计还是非常清晰。如果说,主循环ev_run是libev这棵大树的主干,那么功能强大,数量繁多的watcher就是这棵大树的树叶,而循环上下文ev_loop则是连接主干和树叶的树枝,它们的分工与职责是相当明确的。作为大树的主干,ev_run的代码是非常稳定和干净的,基本上不会掺杂外部开发者的逻辑代码进来。作为叶子的watcher,它的定位也非常明确:专注于自己的领域,只解决某一个类型的问题。不同的watcher以及watcher和主循环之间并没有太多的干扰和耦合,这也是libev能如此灵活扩展的原因。而中间的树枝ev_loop的作用也是不言而喻的,正是因为它在中间的调和,前面两位哥们才能活得这么有个性。

看到这里,libev的主体架构已经比较清楚了,但是似乎还没看到与性能相关的关键代码。与主干代码不一样,这些代码更多的是隐藏在实现具体逻辑的地方,也就是watcher之中的。虽然watcher的使用接口都比较相似,但是不同的watcher,底层的数据结构和处理策略还是不一样的。下面一篇文章我们就来探索一下libev中比较常用的几种watcher的设计与实现。

http://blog.csdn.net/w616589292/article/details/45503057

libev整体设计的更多相关文章

  1. [转]Libev源码分析 -- 整体设计

    Libev源码分析 -- 整体设计 libev是Marc Lehmann用C写的高性能事件循环库.通过libev,可以灵活地把各种事件组织管理起来,如:时钟.io.信号等.libev在业界内也是广受好 ...

  2. Mybatis原理分析之二:框架整体设计

    1.引言 本文主要讲解Mybatis的整体程序设计,理清楚框架的主要脉络.后面文章我们再详细讲解各个组件. 2.整体设计 2.1 总体流程 (1)加载配置并初始化       触发条件:加载配置文件 ...

  3. 用thinkphp进行微信开发的整体设计思考

    用thinkphp进行微信开发的整体设计思考 http://www.2cto.com/weixin/201504/388423.html 2015-04-09      0个评论       作者:明 ...

  4. legend2---开发日志1(legend的数据库整体设计思路是什么)

    legend2---开发日志1(legend的数据库整体设计思路是什么) 一.总结 一句话总结:不同种类的物品分不同的表放,不放到一个物品表里,取所有物品时一个个表的取就好了 不同种类的物品分不同的表 ...

  5. RecyclerView源码分析(一)--整体设计

    RecyclerView这个控件出来已经有一段时间了,如果看这篇文章的你,还没有使用过这个控件.那请先去学习怎样使用.不然看也白看.这里奉上一些关于介绍RecyclerView使用方法的优秀博客: 鸿 ...

  6. ibatis源码学习1_整体设计和核心流程

    背景介绍ibatis实现之前,先来看一段jdbc代码: Class.forName("com.mysql.jdbc.Driver"); String url = "jdb ...

  7. Alink漫谈(十二) :在线学习算法FTRL 之 整体设计

    Alink漫谈(十二) :在线学习算法FTRL 之 整体设计 目录 Alink漫谈(十二) :在线学习算法FTRL 之 整体设计 0x00 摘要 0x01概念 1.1 逻辑回归 1.1.1 推导过程 ...

  8. MindInsight训练可视整体设计介绍

    MindInsight训练可视整体设计介绍 MindInsight是MindSpore的可视化调试调优组件.通过MindInsight可以完成训练可视.性能调优.精度调优等任务. 训练可视功能主要包括 ...

  9. [三]JavaIO之IO体系类整体设计思路 流的概念以及四大基础分类

    从本文开始,将正式进入JavaIO的简介 在继续javaIO系列的文章之前 可以过去看一下 本人博客上的设计模式中的 适配器模式和装饰器模式 这会对接下来的阅读大有帮助   本文是从逻辑上介绍整个的J ...

随机推荐

  1. 【26.34%】【codeforces 722A】Broken Clock

    time limit per test1 second memory limit per test256 megabytes inputstandard input outputstandard ou ...

  2. ios开发网络学习十一:NSURLSessionDataTask离线断点下载(断点续传)

    #import "ViewController.h" #define FileName @"121212.mp4" @interface ViewControl ...

  3. Hadoop文件的基本操作 分类: A1_HADOOP 2014-12-05 11:36 392人阅读 评论(0) 收藏

    Hadoop提供了大量的API对文件系统中的文件进行操作,主要包括: (1)读取文件 (2)写文件 (3)读取文件属性 (4)列出文件 (5)删除文件 1、读取文件 以下示例中,将hdfs中的一个文件 ...

  4. 走进windows编程的世界-----对话框、文本框、button

    1 对话框的分类  2 对话框的基本使用方式  3 对话框资源  4 有模式对话框的使用 int DialogBox( HINSTANCE hInstance, LPCTSTR lpTemplate, ...

  5. Android 开发--CMakeList调用本地so文件

    这里写代码片Android开发常常遇到Java调用so文件的情况,本文介绍一下Google最近新推出的应用在android studio中的方法–cmakelist.txt格式调用. so文件分为jn ...

  6. 【物理/数学】概念的理解 —— pivot、position

    0. 基本释义 pivot: n. 枢轴:中心点:旋转运动 vt. 以-为中心旋转:把-置于枢轴上 vi. 在枢轴上转动:随-转移 adj. 枢轴的:关键的 position: n. 位置,方位:职位 ...

  7. Android Error:(1,N1) 错误: 需要class, interface或enum

    造成这个error的原因是Java文件编码格式不对, 比如可能是你之前这个文件是用GBK写的,后来复制到utf-8环境里编译,而文件里有些是隐藏的字符,很难找出来的. 解决方法是在Notepad++新 ...

  8. nas docker xunlei

    http://bbs.xunlei.com/thread-389018-1-1.html 首先,启动远程服务,找到你的设备IP,在浏览器里输入以下地址,host换成你设备的IP http://host ...

  9. JavaMail| JavaMail配置属性

    属性名 含义 mail.smtp.user SMTP的缺省用户名. mail.smtp.host 要连接的SMTP服务器. mail.smtp.port 要连接的SMTP服务器的端口号,如果conne ...

  10. COCOS学习笔记--单点触控

    这篇博客来总结下cocos单点触控的相关内容: 我们在Layer类的源代码中能够看到.Layer类提供了一下4个与屏幕单点触控相关的回调方法: onTouchBegan().onTouchMoved( ...