一. Reactor模式简介

Reactor释义“反应堆”,是一种事件驱动机制。和普通函数调用的不同之处在于:应用程序不是主动的调用某个API完成处理,而是恰恰相反,Reactor逆置了事件处理流程,应用程序需要提供相应的接口并注册到Reactor上,如果相应的时间发生,Reactor将主动调用应用程序注册的接口,这些接口又称为“回调函数”。

二. moduo库Reactor模式的实现

muduo主要通过3个类来实现Reactor模式:EventLoop,Channel,Poller。

1. EventLoop

  事件循环。moduo的线程模型为one loop per thread,即每个线程只能有一个EventLoop对象。EventLoop对象的生命周期通常和其所属的线程一样长。

数据成员:

const pid_t threadId_;保存当前EventLoop所属线程id

boost::scoped_ptr poller_; 实现I/O复用 boost::scoped_ptr timerQueue_;

int wakeupFd_;

       boost::scoped_ptr wakeupChannel_; 用于处理wakeupFd_上的可读事件,将事件分发到handlRead() ChannelList activeChannels_; 有事件就绪的              Channel Channel* currentActiveChannel_;

       MutexLock mutex_; pendingFunctors_回暴露给其他线程,所以需要加锁 std::vectorpendingFunctors_; 

主要功能函数:

loop(),在该函数中会循环执行以下过程:调用Poller::poll(),通过此调用获得一个vector<channel*>activeChannels_的就绪事件集合,再遍历该容器,执行每个Channel的Channel::handleEvent()完成相应就绪事件回调,最后执行pendingFunctors_排队的函数。上述一次循环就是一次Reactor模式完成。

runInLoop(boost::function<void()>),实现用户指定任务回调,若是EventLoop隶属的线程调用EventLoop::runInLoop()则EventLoop马上执行;若是其它线程调用则执行EventLoop::queueInLoop(boost::function<void()>将任务添加到队列中(线程转移)。EventLoop如何获得有任务这一事实呢?通过eventfd可以实现线程间通信,具体做法是:其它线程向EventLoop::vector<boost::function<void()> >添加任务T,然后通过EventLoop::wakeup()向eventfd写一个int,eventfd的回调函数EventLoop::handleRead()读取这个int,从而相当于EventLoop被唤醒,此时loop中遍历队列执行堆积的任务。这里采用Channel管理eventfd,Poller侦听eventfd体现了eventfd可以统一事件源的优势。

queueInLoop(Functor& cb),将cb放入队列,并在必要时唤醒IO线程。有两种情况需要唤醒IO线程,1 调用queueInLoop()的线程不是IO线程,2 调用queueInLoop()的线程是IO线程,而此时正在调用pengding functor。

2. Channel

  事件分发器。每个Channel只属于一个EventLoop,每个Channel只负责一个文件描述符fd的IO事件分发,但其不拥有fd。

数据成员:

int fd_文件描述符,

int events_ 文件描述符注册事件,

int revents_文件描述符的就绪事件,由Poller::poll设置

readCallback_,writeCallback...各种事件回调,会在拥有该Channel类的构造函数中被注册,例如TcpConnction会在构造函数中TcpConnection::handlRead()注册给Channel::readCallback

主要功能函数:

setCallback()系列函数,接受Channel所属的类注册相应的事件回调函数

     enableReading(),update(), 当一个fd想要注册可读事件时,首先通过Channel::enableReading()-->Channel::update(this)->EventLoop::updateChannel(Channel)->Poller::updateChannel(Channel*)调用链向poll系统调用的侦听事件表注册或者修改注册事件。

handleEvent(), Channel作为是事件分发器其核心结构是Channel::handleEvent(),该函数调用Channel::handleEventWithGuard(),在其内根据Channel::revents的值分发调用相应的事件回调。

3. Poller

  Poller是IO multiplexing的封装,封装了poll和epoll。Poller是EventLoop的间接成员,只供拥有该Poller的EventLoop在IO线程调用。生命期与EventLoop相等。

数据成员:

vector pollfds_事件结构体数组用于poll的第一个参数;

map<int,channel*> channels_用于文件描述符fd到Channel的映射便于快速查找到相应的Channel

主要功能函数:

updateChannel(Channel*) 用于将传入的Channel关心的事件注册给Poller。

poll(int timeoutMs,vector<channel*> activeChannels)其调用poll侦听事件集合,将就绪事件所属的Channel调用fillActiveChannels()加入到activeChannels_中。

其他类

EventLoopThread: 启动一个线程执行一个EventLoop,其语义和"one loop per thread“相吻合。注意这里用到了互斥量和条件变量,这是因为线程A创建一个EventLoopThread对象后一个运行EventLoop的线程已经开始创建了,可以通过EventLoopThread::startLoop()获取这个EventLoop对象,但是若EventLoop线程还没有创建好,则会出错。所以在创建EventLoop完成后会执行condititon.notify()通知线程A,线程A调用EventLoopThread::startLoop()时调用condition.wai()等待,从而保证获取一个创建完成的EventLoop.毕竟线程A创建的EventLoop线程,A可能还会调用EventLoop执行一些任务回调呢。

转自:http://www.cnblogs.com/nicganon/p/3741609.html

muduo库源码剖析(一) reactor模式的更多相关文章

  1. muduo库源码剖析(二) 服务端

    一. TcpServer类: 管理所有的TCP客户连接,TcpServer供用户直接使用,生命期由用户直接控制.用户只需设置好相应的回调函数(如消息处理messageCallback)然后TcpSer ...

  2. caffe库源码剖析——net层

    net层的功能实现主要涉及到net.hpp和net.cpp文件,让我们要捋顺它是干了什么,是如何实现的. 1. net层使用到的参数 第一步要做的事,就是查看caffe.proto文件,弄清楚net都 ...

  3. ucore 源码剖析

    lab1 源码剖析 从实模式到保护模式 初始化ds,es和ss等段寄存器为0 使能A20门,其中seta20.1写数据到0x64端口,表示要写数据给8042芯片的Output Port;seta20. ...

  4. Golang 源码剖析:log 标准库

    Golang 源码剖析:log 标准库 原文地址:Golang 源码剖析:log 标准库 日志 输出 2018/09/28 20:03:08 EDDYCJY Blog... 构成 [日期]<空格 ...

  5. 设计模式(二十二)——状态模式(APP抽奖活动+借贷平台源码剖析)

    24.1 APP 抽奖活动问题 请编写程序完成 APP 抽奖活动 具体要求如下: 1) 假如每参加一次这个活动要扣除用户 50 积分,中奖概率是 10% 2) 奖品数量固定,抽完就不能抽奖 3) 活动 ...

  6. Apache Spark源码剖析

    Apache Spark源码剖析(全面系统介绍Spark源码,提供分析源码的实用技巧和合理的阅读顺序,充分了解Spark的设计思想和运行机理) 许鹏 著   ISBN 978-7-121-25420- ...

  7. DICOM医学图形处理:storescp.exe与storescu.exe源码剖析,学习C-STORE请求(续)

    转载:http://blog.csdn.net/zssureqh/article/details/39237649 背景: 上一篇博文中,在对storescp工具源文件storescp.cc和DcmS ...

  8. DICOM医学图像处理:storescp.exe与storescu.exe源码剖析,学习C-STORE请求

    转载:http://blog.csdn.net/zssureqh/article/details/39213817 背景: 上一篇专栏博文中针对PACS终端(或设备终端,如CT设备)与RIS系统之间w ...

  9. 《Apache Spark源码剖析》

    Spark Contributor,Databricks工程师连城,华为大数据平台开发部部长陈亮,网易杭州研究院副院长汪源,TalkingData首席数据科学家张夏天联袂力荐1.本书全面.系统地介绍了 ...

随机推荐

  1. JavaScript 正则表达式(转自 mozilla)

    正则表达式是被用来匹配字符串中的字符组合的模式.在JavaScript中,正则表达式也是对象. 这种模式可以被用于 RegExp 的 exec 和 test 方法以及 String 的 match.r ...

  2. js作用域、异步——学习笔记

    所有的 for if switch while do 等等,都属于块级作用域,里面声明的对象,外面也能访问.但function 函数里的作用域,在函数外是访问不到的. 但函数作用域里面可以访问外面. ...

  3. 常用的几个Dos命令-持续更新中

    1.服务相关 (1).查看服务 C:\Windows\system32>net start 已经启动以下 Windows 服务: (2).启动服务 C:\Windows\system32> ...

  4. NodeJs学习记录(三)vscode下启动一个nodejs的web工程

    2017/01/23 星期一 前言:根据手上现成的一个web工程来学习 1.配置vscode使其可以识别nodejs的页面文件.ejs 2.先把项目拖拽至vscode的编辑界面,在"查看&q ...

  5. How a stack frame works 栈帧的要素与构建步骤

    http://en.citizendium.org/wiki/Stack_frame To use a stack frame, a thread keeps two pointers, often ...

  6. wpf 自定义Button按钮

    创建ButtonEx类 public class ButtonEx : Button { static ButtonEx() { DefaultStyleKeyProperty.OverrideMet ...

  7. JS数组——冒泡、插入、快速排序

    前言:因为要对后端返回来的数据进行处理,之前之后冒泡,不够用,去看了插入跟快速,写下这篇笔记. 使用背景: 1.冒泡排序 数据比较少,小于1000 2.插入排序 数据比较少,大于1000不推荐 3.快 ...

  8. Spring资源访问接口Resource

    该接口拥有对不同资源类型的实现类 boolean exists() 资源是否存在 boolean isOpen() 资源是否打开 URL getURL() 如果底层资源可以表示成URL,则该方法返回对 ...

  9. mybatis传参总结

    注:文章引用部分 mybatis传递参数总结文章内容 一.单个参数 1.基本数据类型 (1)直接使用 List<ChargeRuleDO> tests(long id); <sele ...

  10. Python学习-比较运算符和逻辑运算符

    比较运算符 == 等于 - 比较对象是否相等 print(3 == 4); //False != 不等于 - 比较两个对象是否不相等 print(3 != 4); // True    <> ...