libev是个非常优秀的基于事件的循环库,很多开源软件,比如nodejs就是使用其实现基础功能。本系列将对该库进行源码分析。(转载请指明出于breaksoftware的csdn博客)

不知道是被墙了还是网站不再维护,它的官网(http://libev.schmorp.de/)在国内已经没法访问了。但是我们仍然可以从github上下载其源码(https://github.com/enki/libev)。

使用样例

libev支持相对时间定时器、绝对时间定时器、文件状态监控和信号监控等功能。我们可以在它基础上,通过少量的代码实现稳健完善的功能。

我们先看一段实现定时器功能的代码

#include <ev.h>
#include <stdio.h>ev_timer timeout_watcher;static void
timeout_cb(EV_P_ ev_timer *w, int revents)
{puts("timeout");ev_break(EV_A_ EVBREAK_ONE);
}int main(void)
{struct ev_loop *loop = EV_DEFAULT;ev_timer_init(&timeout_watcher, timeout_cb, 5.5, 0);ev_timer_start(loop, &timeout_watcher);ev_run(loop, 0);return 0;
}

这段代码的结构非常简单。首先我们要定义一个名为timeout_cb的回调函数用于响应定时器。然后定义一个ev_timer结构(监视器),它通过ev_timer_init进行初始化。初始化的参数包含之前定义的响应函数指针和迭代器超时时间。ev_timer准备好后,通过ev_timer_start将其和一个ev_loop进行绑定。最后使用ev_run方法运行起来这个ev_loop指针,从而实现一个完整的定时器功能。

可见使用libev库非常方便。其实我们之后见到的其他用法和上面步骤是类似的,即:

  • 初始化ev_loop。
  • 定义监视器。
  • 定义回调函数。
  • 监视器和回调函数关联。
  • 监视器和ev_loop关联。
  • ev_run将ev_loop运行起来。

假如上面代码是个框架使用的雏形,那么如果让我们去设计这样的框架,该如何设计呢?

模型设计

首先我们需要考虑到的是使用sleep还是使用事件模型去实现逻辑等待。

如果使用sleep方法,那么我们就要在唤醒后去检测各个事件,比如要检测文件状态是否发生变化,比如定时器时间是否已经超时。于是有个问题,就是sleep多久怎么确定?我们不知道是5秒后还是1秒后文件状态发生变化,那么只能最小粒度sleep了。那么这就意味着线程在短暂挂起后,马上检测一系列可能尚未发生改变的事件。这种设计明显很消耗CPU,而且非常低效。

如果使用事件模型去等待,就可以解决上述问题。但是像定时器,在系统中并没有事件与其对应。于是我们需要考虑下对于没有事件对应的功能怎么通过事件模型去封装。

其次我们需要考虑使用单线程模型还是多线程模型。

单线程模型是让主流程和事件响应函数在一个线程中执行。其伪代码是

If (event is ready) {event_callback(); // in the main thead
}

其特点是实现简单,但是事件响应函数的效率将严重影响主流程对事件的响应速度。比如A、B两个事件同时发生,理论上我们希望两个事件的响应函数被同时执行,或者在允许存在的系统调用时间差(比如创建线程的消耗)内执行。然而单线程模型则会让一个响应函数执行完后再去执行另一响应函数,于是就出现排队现象。所以单线程模型无法保证及时响应。

多线程模型则完全避免了上述问题。它可在事件发生后启动一个线程去处理响应函数。当然相对来说多线程模型比较复杂,需要考虑多线程同步问题。

If (event is ready) {thread_excute(event_callback); // run in another thread
}

那么libev对上面两个问题是怎么选择的呢?对于sleep和事件模型,libev选择的是后者,所以它是“高性能”的。对于单线程和多线程,libev选择的是前者。至于原因我并不知道,可能是作者希望它足够简单,或者希望它能在不支持多线程的系统上使用。但是要说明一点,并不是说libev不支持多线程。因为一个单线程模型的执行体,是可以放在其他若干个线程中执行的,只要保证数据同步。

单/多线程编译

libev提供了各种编译选项以支持各种特性。比如在支持多线程的系统上,我们可以指定EV_MULTIPLICITY参数,以让libev编译出多线程版本。

libev对于单线程版本的数据都是以全局静态变量形式提供。而对于多线程版本,则提供了一个结构体——ev_loop保存数据,这样不同线程持有各自的数据对象,从而做到数据隔离。

#if EV_MULTIPLICITYstruct ev_loop{ev_tstamp ev_rt_now;#define ev_rt_now ((loop)->ev_rt_now)#define VAR(name,decl) decl;#include "ev_vars.h"#undef VAR};#include "ev_wrap.h"static struct ev_loop default_loop_struct;EV_API_DECL struct ev_loop *ev_default_loop_ptr = 0; /* needs to be initialised to make it a definition despite extern */#elseEV_API_DECL ev_tstamp ev_rt_now = 0; /* needs to be initialised to make it a definition despite extern */#define VAR(name,decl) static decl;#include "ev_vars.h"#undef VARstatic int ev_default_loop_ptr;#endif

不管是哪个版本,它们都提供了ev_default_loop_ptr变量。多线程版本它将指向全局静态变量default_loop_struct,这样对于使用了多线程版本又不想维护ev_loop结构对象的用户来说,直接使用这个对象就行了,非常方便。

然后再看下ev_vars.h的引入。其实现如下:

#define VARx(type,name) VAR(name, type name)VARx(ev_tstamp, now_floor) /* last time we refreshed rt_time */
VARx(ev_tstamp, mn_now)    /* monotonic clock "now" */
VARx(ev_tstamp, rtmn_diff) /* difference realtime - monotonic time *//* for reverse feeding of events */
VARx(W *, rfeeds)
VARx(int, rfeedmax)
VARx(int, rfeedcnt)VAR (pendings, ANPENDING *pendings [NUMPRI])
VAR (pendingmax, int pendingmax [NUMPRI])
VAR (pendingcnt, int pendingcnt [NUMPRI])
……

在多线程版本中,它在ev_loop结构体中被引入的。这样在编译器展开文件时,它将会被定义到结构体内部。在单线程版本中,VAR宏被声明为定义一个静态全局变量的形式。这种利用宏和编译展开技术,在不同结构中定义相同类型数据的方式还是很有意思的。

但是又会有个问题,如何去访问这些变量呢?在单线程中,它们是静态变量,所有位置可以直接通过名称访问。而多线程版本中,则需要通过一个ev_loop结构体去引导。相关的代码总不能通过EV_MULTIPLICITY宏来区分不同变量形式吧?如果那样,代码将变得非常难看。我们看下libev怎么巧妙解决这个问题的。

上面代码块的多线程定义区间,引入了ev_wrap.h文件。其实现如下:

#ifndef EV_WRAP_H
#define EV_WRAP_H
#define acquire_cb ((loop)->acquire_cb)
#define activecnt ((loop)->activecnt)
#define anfdmax ((loop)->anfdmax)
#define anfds ((loop)->anfds)
#define async_pending ((loop)->async_pending)
#define asynccnt ((loop)->asynccnt)
……

这样使用一个和变量相同名称的宏替代了通过ev_loop结构体对象访问的变量。且这个宏名称和单线程版本中静态变量名相同。这样就让不同版本的关键变量“同名”了。于是代码对这些变量的访问直接使用其原始名称即可——单线程中使用的是真实变量名,多线程中使用的是宏。
        这样的设计,又引入一个问题。那就是所有使用这些变量的函数,在多线程版本中,需要提供一个名字为loop的ev_loop结构体对象;而在单线程版本中则不需要。为了固化这个名称,libev还为此专门定义了一系列宏。

#if EV_MULTIPLICITY
struct ev_loop;
# define EV_P  struct ev_loop *loop               /* a loop as sole parameter in a declaration */
# define EV_P_ EV_P,                              /* a loop as first of multiple parameters */
# define EV_A  loop                               /* a loop as sole argument to a function call */
# define EV_A_ EV_A,                              /* a loop as first of multiple arguments */
# define EV_DEFAULT_UC  ev_default_loop_uc_ ()    /* the default loop, if initialised, as sole arg */
# define EV_DEFAULT_UC_ EV_DEFAULT_UC,            /* the default loop as first of multiple arguments */
# define EV_DEFAULT  ev_default_loop (0)          /* the default loop as sole arg */
# define EV_DEFAULT_ EV_DEFAULT,                  /* the default loop as first of multiple arguments */
#else
# define EV_P void
# define EV_P_
# define EV_A
# define EV_A_
# define EV_DEFAULT
# define EV_DEFAULT_
# define EV_DEFAULT_UC
# define EV_DEFAULT_UC_
# undef EV_EMBED_ENABLE
#endif

之后我们在代码中导出可见的EV_P和EV_A就是为了保证不同版本的实现在代码层面是相同的。

libev源码解析——总览相关推荐

  1. libev源码解析——定时器原理

    本文将回答<libev源码解析--I/O模型>中抛出的两个问题.(转载请指明出于breaksoftware的csdn博客) 对于问题1:为什么backend_poll函数需要指定超时?我们 ...

  2. libev源码解析——I/O模型

    在<libev源码解析--总览>一文中,我们介绍过,libev是一个基于事件的循环库.本文将介绍其和事件及循环之间的关系.(转载请指明出于breaksoftware的csdn博客) 目前i ...

  3. libev源码解析——监视器(watcher)结构和组织形式

    在<libev源码解析--总览>中,我们介绍了libev的一些重要变量在不同编译参数下的定义位置.由于这些变量在多线程下没有同步问题,所以我们将问题简化,所提到的变量都是线程内部独有的,不 ...

  4. libev源码解析——定时器监视器和组织形式

    我们先看下定时器监视器的数据结构.(转载请指明出于breaksoftware的csdn博客) /* invoked after a specific time, repeatable (based o ...

  5. libev源码解析——调度策略

    在<libev源码解析--监视器(watcher)结构和组织形式>中介绍过,监视器分为[2,-2]区间5个等级的优先级.等级为2的监视器最高优,然后依次递减.不区分监视器类型和关联的文件描 ...

  6. libev 源码解析

    一  libev简介 libev是一个轻量级的事件通知库,具备支持多种事件通知能力,通过对libev的源码的阅读,可以清楚了解事件通知实现内部机制. 二 核心数据结构 在libev中关键的数据结构是, ...

  7. 源码解析:Spring源码解析笔记(五)接口设计总览

    本文由colodoo(纸伞)整理 QQ 425343603 Java学习交流群(717726984) Spring解析笔记 启动过程部分已经完成,对启动过程源码有兴趣的朋友可以作为参考文章. 源码解析 ...

  8. Soul网关源码解析目录

    Soul网关源码解析目录 Soul网关源码解析文章列表     对用Java写的高性能网关:Soul,进行一波学习和研究,下面是相关的文章记录 掘金 了解与初步运行 Soul网关源码解析(一) 概览 ...

  9. SeaTunnel 2.1.2的源码解析(5)seatunnel-connectors-flink-http

    SeaTunnel 2.1.2的源码解析(5)seatunnel-connectors-flink-http 本文已参与「开源摘星计划」,欢迎正在阅读的你加入.活动链接:https://github. ...

最新文章

  1. python array赋值_从踩坑学Python内部原理(5):执行时机的差异
  2. Esper——内存计算、事件驱动、SQL支持
  3. thonny中文版(python编辑器)
  4. spring boot 邮件端口_不会吧,SpringBoot还能发送邮件发送邮件?快来看看如何操作吧
  5. 在VS2017中用c#编写程序执行python遇到中文不能处理出现乱码的情况
  6. 二元隐函数求二阶偏导_在线计算专题(03):具体、抽象函数的导数、微分与方向导数的计算...
  7. FG60B SSL ××× V3.0配置
  8. 计算机主机的奥秘,揭秘网吧电脑为何流畅的奥秘
  9. Python 对字典循环遍历的两种方式
  10. 是的,解禁了,是胜利还是嘲讽?
  11. 深入理解JavaScript系列(31):设计模式之代理模式
  12. Python Django 之 jQuery
  13. Linux之shell命令
  14. 计算机aero背景黑,win7系统如何打开和关闭Aero特效
  15. 20172301 2017-2018-2 《程序设计与数据结构》第六周学习总结
  16. Win11电脑系统使用U盘安装的方法分享
  17. 启动kafka2.8报afka.common.KafkaException: No `meta.properties` found in /tmp/kraft-combined-logs
  18. o7_dictionary_accessibility 参数
  19. php微信当面付,微信当面付(c#源码)
  20. qq公众平台出错了609_微信附近人功能无法使用,到底是平台出了差错还是由于自我原因。...

热门文章

  1. Python 比较俩张图片差异
  2. keras 的 example 文件 lstm_stateful.py 解析
  3. MSP430低功耗模式-while循环失效
  4. tftp:timeout问题解决 - 从Windows传输文件到开发板
  5. 兰州大学C语言程序设计课程作业,【兰州大学|兰州大学C语言程序设计课程作业( 五 )】语言程序设计|课程|作业-傻大方...
  6. smali to java_Smali —— 数学运算,条件判断,循环
  7. 剑指offer:面试题38. 字符串的排列
  8. 【实用】一组原创原生样式的基础控件、UI组件样式(偏向移动端H5页面的样式)
  9. C++ STL: lower_bound 和 upper_bound
  10. 深入理解ceph-disk prepare 源码逻辑