muduo::EventLoop分析

EventLoop是整个Reactor的核心。其类图如下:

one loop per thread意味着每个线程只能有一个EventLoop对象,用变量

__thread EventLoop* t_loopInThisThread = 0;

表示,在创建EventLoop对象时将t_loopInThisThread赋值,以后再创建时就可以检查这个变量,如果已经赋值就说明当前线程已经创建过EventLoop对象了。线程调用静态函数EventLoop::getEventLoopOfCurrentThread就可以获得当前线程的EventLoop对象的指针了。

EventLoop有许多变量,几个bool变量,looping_:是否正在执行loop循环;quit_:是否已经调用quit()函数退出loop循环;eventHandling是否正在处理event事件;callingPendingFunctors是否正在调用pendingFunctors_的函数对象。

其他变量,poller_是用来调用pool或epool的,activeChannels_记录这激活事件的集合,currentActiveChannel_是当前正在处理的channel事件,pendingFunctors_是当前线程要执行任务的集合。可以在loop()函数中看到这一点:

void EventLoop::loop()//EventLoop在这里循环
{
  assert(!looping_);
  assertInLoopThread();
  looping_ = true;
  quit_ = false;  // FIXME: what if someone calls quit() before loop() ?
  LOG_TRACE << "EventLoop " << this << " start looping";

  while (!quit_)
  {
    activeChannels_.clear();//清空激活事件集合
    pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_);//pool_wait或epoll_wait。阻塞在这里
    ++iteration_;
    if (Logger::logLevel() <= Logger::TRACE)
    {
      printActiveChannels();
    }
    // TODO sort channel by priority
    eventHandling_ = true;
    for (ChannelList::iterator it = activeChannels_.begin();
        it != activeChannels_.end(); ++it)
    {
      currentActiveChannel_ = *it;
      currentActiveChannel_->handleEvent(pollReturnTime_);//事件处理I/O
    }
    currentActiveChannel_ = NULL;
    eventHandling_ = false;
    doPendingFunctors();
  }

  LOG_TRACE << "EventLoop " << this << " stop looping";
  looping_ = false;
}

线程在poller_->poll等待监听事件的到来,当poller_->poll返回后,监听到的事件放到了activeChannel中,随后一一处理激活事件。

最后面调用doPendingFunctors()是执行pendingFunctors_中的任务。

void EventLoop::doPendingFunctors()//执行任务队列中的任务
{
  std::vector<Functor> functors;
  callingPendingFunctors_ = true;

  {
  MutexLockGuard lock(mutex_);//尽量让临界区小
  functors.swap(pendingFunctors_);
  }

  for (size_t i = 0; i < functors.size(); ++i)
  {
    functors[i]();
  }
  callingPendingFunctors_ = false;
}

EventLoop的owner线程除了等待poll、执行poll返回的激活事件,还可以处理一些其他任务,例如调用某一个回调函数,处理其他EventLoop对象的,调用void EventLoop::runInLoop(const Functor& cb)即可让EventLoop的owner线程执行cb函数。

void EventLoop::runInLoop(const Functor& cb)//可以夸线程调用
{
  if (isInLoopThread())//当前线程是ower线程则立即执行,否则放到ower线程的任务队列,异步执行
  {
    cb();
  }
  else
  {
    queueInLoop(cb);
  }
}

void EventLoop::queueInLoop(const Functor& cb)
{
  {
  MutexLockGuard lock(mutex_);
  pendingFunctors_.push_back(cb);
  }
//不是EventLoop的owner线程,或者是当前线程,但是正在执行任务队列中的任务
  if (!isInLoopThread() || callingPendingFunctors_)
  {
    wakeup();//唤醒owner线程
  }
}

如果时EventLoop的owner线程,会调用runInLoop会立即执行回调函数cb,否则会把回调函数放到任务队列(其实时vector),即调用queueInLoop函数。如果不是当前线程调用,或者正在执行pendingFunctors_中的任务,都要唤醒EventLoop的owner线程,让其执行pendingFunctors_中的任务。如果正在执行pendingFunctors_中的任务,添加新任务后不会执行新的任务,因为functors.swap(pendingFunctors_)后,执行的时functors中的任务。

这里的唤醒wakeup()用了eventfd。这是2.6内核新增的一个技术。

#include <sys/eventfd.h>
int eventfd(unsigned int initval, int flags);  

创建一个eventfd的fd后,就可以对它进行read、write等操作。eventfd相当于一个计数器,read以后计数器清零,write递增计数器;fd可以进行如下操作:select(poll、epoll)、close操作。

EventLoop中的wakeupFd_就是eventfd,wakeupChannel_和wakeupFd_相关联,EventLoop关注了wakeupChannel_的读事件,当要唤醒(即poller_->poll)时,写wakeupFd_即可。

void EventLoop::wakeup()//wakeupFd写,唤醒读wakeupFd的线程
{
  uint64_t one = 1;
  ssize_t n = sockets::write(wakeupFd_, &one, sizeof one);
  if (n != sizeof one)
  {
    LOG_ERROR << "EventLoop::wakeup() writes " << n << " bytes instead of 8";
  }
}

EventLoop中还有定时器,可以在某一时刻(runAt),未来多久(runAfter),每隔多久(runEvery)执行某一函数。定时用到了TimerQueue,稍后分析它。

TimerId EventLoop::runAt(const Timestamp& time, TimerCallback&& cb)
{
  return timerQueue_->addTimer(std::move(cb), time, 0.0);
}

TimerId EventLoop::runAfter(double delay, TimerCallback&& cb)
{
  Timestamp time(addTime(Timestamp::now(), delay));
  return runAt(time, std::move(cb));
}

TimerId EventLoop::runEvery(double interval, TimerCallback&& cb)
{
  Timestamp time(addTime(Timestamp::now(), interval));
  return timerQueue_->addTimer(std::move(cb), time, interval);
}

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-11 03:38:18

muduo::EventLoop分析的相关文章

muduo::TcpConnection分析

TcpConnection是使用shared_ptr来管理的类,因为它的生命周期模糊.TcpConnection表示已经建立或正在建立的连接,状态只有kConnecting.kConnected.kDisconnected.kDisconnecting,它初始化时,构造函数的sockfd表示正在建立连接kConnecting. 建立连接后,用户只需处理收发数据,发送数据它会自动处理,收取数据后,会调用用户设置的MessageCallback函数来处理收到的数据. TcpConnection中封装

muduo源代码分析--Reactor模式在muduo中的使用

一. Reactor模式简单介绍 Reactor释义"反应堆",是一种事件驱动机制.和普通函数调用的不同之处在于:应用程序不是主动的调用某个API完毕处理.而是恰恰相反.Reactor逆置了事件处理流程,应用程序须要提供对应的接口并注冊到Reactor上,假设对应的时间发生,Reactor将主动调用应用程序注冊的接口,这些接口又称为"回调函数". 二. moduo库Reactor模式的实现 muduo主要通过3个类来实现Reactor模式:EventLoop,Cha

muduo::ThreadPoll分析

线程池本质上是一个生产者消费者的模型.在线程池有一个存放现场的ptr_vector,相当于消费者;有一个存放任务的deque,相当于仓库.线程(消费者)去仓库取任务,然后执行;当有新程序员是生产者,当有新任务时,就把任务放到deque(仓库). 任务队列(仓库)是有边界的,所以在实现时需要有两个信号量,相当与BoundedBlockingQueue. 每个线程在第一次运行时都会调用一次回调函数threadInitCallback_,为线程执行做准备. 在线程池开始运行之前,要先设置任务队列的大小

muduo源代码分析--Reactor在模型muduo使用(两)

一. TcpServer分类: 管理所有的TCP客户连接,TcpServer对于用户直接使用,直接控制由用户生活. 用户只需要设置相应的回调函数(消息处理messageCallback)然后TcpServer::start()就可以. 主要数据成员: boost::scoped_ptr<Accepter> acceptor_; 用来接受连接 std::map<string,TcpConnectionPtr> connections_; 用来存储全部连接 connectonCallb

Muduo 设计与实现之一:Buffer 类的设计

[开源访谈]Muduo 作者陈硕访谈实录 http://www.oschina.net/question/28_61182 开源访谈是开源中国推出的一系列针对国内优秀开源软件作者的访谈,以文字的方式记录并传播.我们希望开源访谈能全面的展现国内开源软件.开源软件作者的现状,着实推动国内开源软件的应用与发展. [嘉宾简介] 陈硕 北京师范大学硕士,擅长 C++ 多线程网络编程和实时分布式系统架构.现任职于香港某跨国金融公司 IT 部门,从事实时外汇交易系统开发.编写了开源 C++ 网络库 muduo

muduo网络库预备知识点

TCP网络编程的三个半事件 非阻塞网络编程中应用层要使用缓冲区 发送方应用层为什么使用缓冲区 接收方应用层方为什么使用缓冲 如何设计使用缓冲区 什么是Reactor模式 non-blocking IO IO multiplexing muduo推荐的模式 线程池大小的阻抗匹配原则 Eventloop采用level trigger的原因 前面都在分析muduo/base中的源码,这些是辅助网络库的.在分析网络库前,先总结一下相关知识点. TCP网络编程要关注哪些问题?muduo网络库总结为三个半事

Chapter 4-02

Please indicate the source: http://blog.csdn.net/gaoxiangnumber1 Welcome to my github: https://github.com/gaoxiangnumber1 4.6 多线程与IO ?本书只讨论同步IO,包括阻塞与非阻塞,不讨论异步IO(AIO).在进行多线程网络编程的时候,几个问题是:如何处理IO?能否多个线程同时读写同一个socket文件描述符?我们知道用多线程同时处理多个socket通常可以提高效率,那么用

Chapter 4-01

Please indicate the source: http://blog.csdn.net/gaoxiangnumber1 Welcome to my github: https://github.com/gaoxiangnumber1 第4章C++多线程系统编程精要 ?学习多线程编程面临的思维方式的转变有两点: 1.当前线程可能随时会被切换出去,或者说被抢占(preempt)了. 2.多线程程序中事件的发生顺序不再有全局统一的先后关系1. ?当线程被切换回来继续执行下一条语句(指令)的时

Muduo网络库源码分析(一) EventLoop事件循环(Poller和Channel)

从这一篇博文起,我们开始剖析Muduo网络库的源码,主要结合<Linux多线程服务端编程>和网上的一些学习资料! (一)TCP网络编程的本质:三个半事件 1. 连接的建立,包括服务端接受(accept) 新连接和客户端成功发起(connect) 连接.TCP 连接一旦建立,客户端和服务端是平等的,可以各自收发数据. 2. 连接的断开,包括主动断开(close 或shutdown) 和被动断开(read(2) 返回0). 3. 消息到达,文件描述符可读.这是最为重要的一个事件,对它的处理方式决定