Dispatch类似于一个消息泵,在一个死循环中,不停地检查IO的状态(可以想像成不断从消息队列中读取消息),将状态的改变变成事件,再进行事件的响应。

主要代码如下:

[event.c]

int

event_base_loop(struct event_base *base, int flags)

{

const struct eventop *evsel = base->evsel;

...

done = 0;

base->event_gotterm = base->event_break = 0;

while (!done) {

base->event_continue = 0;

/* Terminate the loop if we have been asked to */

if (base->event_gotterm) {

break;

}

if (base->event_break) {

break;

}

...

/// 在这里调用底层的 dispatch

res = evsel->dispatch(base, tv_p);

...

if (N_ACTIVE_CALLBACKS(base)) {

/// 处理和响应事件

int n = event_process_active(base);

if ((flags & EVLOOP_ONCE)

&& N_ACTIVE_CALLBACKS(base) == 0

&& n != 0)

done = 1;

} else if (flags & EVLOOP_NONBLOCK)

done = 1;

}

done:

...

return (retval);

}

如果不是一次性事件,由while (!done)是一个死循环。这样可以反复地调用底层的dispatch去获取fd的状态. 在dispatch之后,又调用了event_process_active这个重要的函数。后面会讲到,它的作用是调用event绑定的回调函数。

select_dispatch

其主工作是调用了select函数,然后调用 evmap_io_active 触发事件。这个函数的实现有一个小技巧值得学习就是如何对随时变化的集合进行操作。

static int select_dispatch(struct event_base *base, struct timeval *tv)

{

int res=0, i, j, nfds;

struct selectop *sop = base->evbase;

///...

/// 主要工作是调整 event_XXXset_out 与 event_XXXset_in, select()基于前者,而对set的修改基于后者,在select之前有必要做一次同步

memcpy(sop->event_readset_out, sop->event_readset_in,

sop->event_fdsz);

memcpy(sop->event_writeset_out, sop->event_writeset_in,

sop->event_fdsz);

nfds = sop->event_fds+1;

EVBASE_RELEASE_LOCK(base, th_base_lock);

res = select(nfds, sop->event_readset_out,

sop->event_writeset_out, NULL, tv);

EVBASE_ACQUIRE_LOCK(base, th_base_lock);

...

i = random() % nfds;

for (j = 0; j < nfds; ++j) {

if (++i >= nfds)

i = 0;

res = 0;

if (FD_ISSET(i, sop->event_readset_out))

res |= EV_READ;

if (FD_ISSET(i, sop->event_writeset_out))

res |= EV_WRITE;

if (res == 0)

continue;

evmap_io_active(base, i, res);

}

check_selectop(sop);

return (0);

}

上面的代码中,先调用select获取fd的状态。注意一次并非返回一个fd,而操作两个fd的列表:读的fd列表,写的fd列表。接下来对select之后的队列进行操作,如果有读、写事件,则调用evmap_io_active在一个io map中登记,实际是将事件放到active队列中。此时只是登记,还没有调起事件对应的回调函数。

event_process_active

接下来分析如何调用事件对应的回调函数。回到event_base_dispatch(event_base_loop),从调用底层dispatch之后继续分析。到了最后调用了event_process_active。这个函数就是处理就绪队列的,就是它的内部实现调用了event关联的回调函数。event_process_active的处理流程是这样的:

event_process_active -> event_process_active_single_queue->(*ev->callback)(...)

[event.c]

static int event_process_active(struct event_base *base)

{

/* Caller must hold th_base_lock */

struct event_list *activeq = NULL;

int i, c = 0;

for (i = 0; i < base->nactivequeues; ++i) {

if (TAILQ_FIRST(&base->activequeues[i]) != NULL) {

base->event_running_priority = i;

activeq = &base->activequeues[i];

c = event_process_active_single_queue(base, activeq);

if (c < 0) {

base->event_running_priority = -1;

return -1;

} else if (c > 0)

break; /* Processed a real event; do not

* consider lower-priority events */

/* If we get here, all of the events we processed

* were internal.  Continue. */

}

}

event_process_deferred_callbacks(&base->defer_queue,&base->event_break);

base->event_running_priority = -1;

return c;

}

遍历activequeues,对每个项(一个队列),处理此项。

static int

event_process_active_single_queue(struct event_base *base,

struct event_list *activeq)

{

struct event *ev;

int count = 0;

EVUTIL_ASSERT(activeq != NULL);

for (ev = TAILQ_FIRST(activeq); ev; ev = TAILQ_FIRST(activeq)) {

if (ev->ev_events & EV_PERSIST)

event_queue_remove(base, ev, EVLIST_ACTIVE);

else

event_del_internal(ev);

switch (ev->ev_closure) {

case EV_CLOSURE_SIGNAL:

event_signal_closure(base, ev);

break;

case EV_CLOSURE_PERSIST:

event_persist_closure(base, ev);

break;

default:

case EV_CLOSURE_NONE:

EVBASE_RELEASE_LOCK(base, th_base_lock);

(*ev->ev_callback)(

ev->ev_fd, ev->ev_res, ev->ev_arg);

break;

}

...

if (base->event_break)

return -1;

if (base->event_continue)

break;

}

return count;

}

libevent2源码分析之四:libevent2的消息泵的更多相关文章

  1. zookeeper源码分析之四服务端(单机)处理请求流程

    上文: zookeeper源码分析之一服务端启动过程 中,我们介绍了zookeeper服务器的启动过程,其中单机是ZookeeperServer启动,集群使用QuorumPeer启动,那么这次我们分析 ...

  2. RocketMQ源码分析之RocketMQ事务消息实现原理上篇(二阶段提交)

    在阅读本文前,若您对RocketMQ技术感兴趣,请加入 RocketMQ技术交流群 根据上文的描述,发送事务消息的入口为: TransactionMQProducer#sendMessageInTra ...

  3. 【TencentOS tiny】深度源码分析(4)——消息队列

    消息队列 在前一篇文章中[TencentOS tiny学习]源码分析(3)--队列 我们描述了TencentOS tiny的队列实现,同时也点出了TencentOS tiny的队列是依赖于消息队列的, ...

  4. Spark源码分析之四:Stage提交

    各位看官,上一篇<Spark源码分析之Stage划分>详细讲述了Spark中Stage的划分,下面,我们进入第三个阶段--Stage提交. Stage提交阶段的主要目的就一个,就是将每个S ...

  5. spark 源码分析之四 -- TaskScheduler的创建和启动过程

    在 spark 源码分析之二 -- SparkContext 的初始化过程 中,第 14 步 和 16 步分别描述了 TaskScheduler的 初始化 和 启动过程. 话分两头,先说 TaskSc ...

  6. libevent2源码分析之五:关键的调用链

    用一个调用链来表示函数调用的流程,看起来更直观.根据上面的分析,总结了一些重要的调用链. 初始化 event_base_new event_base_new_with_config min_heap_ ...

  7. libevent2源码分析之三:信号的初始化流程

    libevent2对信号的响应也进行了封装,使之与socket操作一样对外提供统一的接口.这里的信号一般指linux的信号.由于信号与socket相关的编程接口有较大的不同,因此在内部实现也有一些区别 ...

  8. libevent2源码分析之一:前言

    event的本质 libevent2中的event的本质是什么?只要是非同步阻塞的运行方式,肯定遵循事件的订阅-发布模型.通过event_new的函数原型可以理解,一个event即代表一次订阅,建立起 ...

  9. libevent2源码分析之二:初始化流程

    本文并不很详细地分析初始化的各个细节,而重点分析如何将底层操作关联到event_base的相关字段.初始化工作主要是针对event_base的.libevent2支持多种底层实现,有epoll, se ...

随机推荐

  1. redis发布订阅、HyperLogLog与GEO功能的介绍

    一.发布订阅 1.模型 发布者发布消息,订阅者接收消息 2.API 2.1.publish 2.2.订阅 2.3.取消订阅 unsubsribe 2.4.其他api 二.HyperLogLog 极小空 ...

  2. 【wordpress】 $wpdb 应用实例

    <?php require_once('e:/php/wordpress/wp-blog-header.php');//注释掉这一句就出错了 global $wpdb; $a = $wpdb-& ...

  3. 如何在Android Studio中创建jniLib和asset文件夹 2

    1.创建asset文件夹 如图进行操作 2.创建jniLib文件夹 —打开app下面的gradle文件(不是project的gradle) —在gradle文件的Android标签里面添加 sourc ...

  4. HDU 1325 Is It A Tree?(并查集)

    题目大意: 给你两个节点,前者指向后者(可以认为前者是后者的父节点),然后让你判断是否是一棵树. 解题思路: 先说说这道题和小希的迷宫(HDU1272)那道题的区别,前者给出的两个点是有方向的,而后者 ...

  5. 训练指南 UVA - 10917(最短路Dijkstra + 基础DP)

    layout: post title: 训练指南 UVA - 10917(最短路Dijkstra + 基础DP) author: "luowentaoaa" catalog: tr ...

  6. 洛谷——P1024 一元三次方程求解

    P1024 一元三次方程求解 题目描述 有形如:ax3+bx2+cx+d=0 这样的一个一元三次方程.给出该方程中各项的系数(a,b,c,d 均为实数),并约定该方程存在三个不同实根(根的范围在-10 ...

  7. 【动态规划】【记忆化搜索】CODEVS 3409 搬运礼物 CodeVS原创

    考虑暴力递归求解的情况: f(i)=min(a(i),f(i-1),f(i-2),...,f(1)) 由于只要参数相同,f()函数的返回值是一样的,因此导致了大量的重复计算,所以我们可以记忆下来. # ...

  8. 十. 图形界面(GUI)设计8.选择框和单选按钮

    选择框.单选框和单选按钮都是选择组件,选择组件有两种状态,一种是选中(on),另一种是未选中(off),它们提供一种简单的 “on/off”选择功能,让用户在一组选择项目中作选择. 选择框 选择框(J ...

  9. golang垃圾回收

    常见GC算法 我总结了一下常见的 GC 算法.分别是:引用计数法.Mark-Sweep法.三色标记法.分代收集法. 1. 引用计数法 原理是在每个对象内部维护一个整数值,叫做这个对象的引用计数,当对象 ...

  10. no such file or directory : 'users/shikx/xxx/xxx/Appirater.m'

    删除此处红色的.m文件即可