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. AC日记——[JSOI2008]火星人prefix bzoj 1014

    1014 思路: 平衡树+二分答案+hash: 好了懂了吧. 代码: #include <cstdio> #include <cstring> #include <ios ...

  2. 记录一次WebService使用的经历

    于业务需要,需要和第三方对接一些接口,但是问题是,他们的接口提供是webservice的,本人只精通restful接口(也就是说我比较年轻^-^).好在对面人特别奈斯,一顿指导我,感谢. 废话不多说了 ...

  3. 将win平台上的mysql数据复制到linux上报错Can't write; duplicate key in table

    将win平台上的mysql数据复制到linux上报错Can't write; duplicate key in table xxx 新年新气象,果然在新年的第一天就遇到了一个大坑,项目在win上跑的没 ...

  4. centos 命令行修改主机名

    # vi /etc/sysconfig/network # 把localhost.localdomain 修改为 localhost.com # 保存退出 # vi /etc/hosts # 把loc ...

  5. flutter ui

    快速生成无状态模板 void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget ...

  6. (1) Flutter android studio安装

    Flutter由两部分组成 Flutter引擎和Flutter框架 Flutter引擎由C++编写,在android上通过NDK编译,在ios上通过llvm编译 Flutter框架由dart编写 1. ...

  7. 线段树+扫描线【HDU1542】Atlantis

    Description 给定一些二维空间上的矩形,求它们的面积并. 一道线段树+扫描线的板子题 然而即使我会打了,也不能灵活运用这种算法.QAQ 遇到题还是不太会. 但是这种板子题还是随随便便切的. ...

  8. 洛谷——P1088 火星人

    P1088 火星人 题目描述 人类终于登上了火星的土地并且见到了神秘的火星人.人类和火星人都无法理解对方的语言,但是我们的科学家发明了一种用数字交流的方法.这种交流方法是这样的,首先,火星人把一个非常 ...

  9. RabbitMQ (四) 工作队列之公平分发

    上篇文章讲的轮询分发 : 1个队列,无论多少个消费者,无论消费者处理消息的耗时长短,大家消费的数量都一样. 而公平分发,又叫 : 能者多劳,顾名思义,处理得越快,消费得越多. 生产者 public c ...

  10. [CF819B]Mister B and PR Shifts

    题意:定义一个排列$p_{1\cdots n}$的“偏移量”$D=\sum _{i=1}^n\left|p_i-i\right|$ 求它所有的轮换排列中偏移量最小的是多少,要求输出轮换序数 暴力就是求 ...