Muduo网络库——EventLoopThreadPool、EventLoopThread、EventLoop浅析

文章目录

  • EventLoopThreadPool
  • EventLoopThread
  • EventLoop

muduo的并发模型为one loop per thread+ threadpool。

EventLoopThread是事件循环线程,包含一个Thread对象,一个EventLoop对象。在构造函数中,把EventLoopThread::threadFunc 注册到Thread对象中(线程启动时会回调)。

EventLoopThreadPool是事件循环线程池,管理所有客户端连接,每个线程都有唯一一个事件循环,可以调用setThreadNum设置线程的数目。

EventLoopThreadPool

EventLoopThreadPool是基于muduo库中Tcpserver这个类专门做的一个线程池,它的模式属于半同步半异步,线程池中每一个线程都有一个自己的EventLoop,而每一个EventLoop底层都是一个poll或者epoll,它利用了自身的poll或者epoll在没有事件的时候阻塞住,在有事件发生的时候,epoll监听到了事件就会去处理事件。


EventLoopThreadPool.h

#ifndef MUDUO_NET_EVENTLOOPTHREADPOOL_H
#define MUDUO_NET_EVENTLOOPTHREADPOOL_H

#include "muduo/base/noncopyable.h"
#include "muduo/base/Types.h"

#include <functional>
#include <memory>
#include <vector>

namespace muduo
{

namespace net
{

class EventLoop;
class EventLoopThread;

class EventLoopThreadPool : noncopyable
{
 public:
  typedef std::function<void(EventLoop*)> ThreadInitCallback;

  EventLoopThreadPool(EventLoop* baseLoop, const string& nameArg);
  ~EventLoopThreadPool();
  void setThreadNum(int numThreads) { numThreads_ = numThreads; }
  void start(const ThreadInitCallback& cb = ThreadInitCallback());

  // valid after calling start()
  /// round-robin
  EventLoop* getNextLoop();

  /// with the same hash code, it will always return the same EventLoop
  EventLoop* getLoopForHash(size_t hashCode);

  std::vector<EventLoop*> getAllLoops();

  bool started() const
  { return started_; }

  const string& name() const
  { return name_; }

 private:

  EventLoop* baseLoop_;//主线程的EventLoop
  string name_;
  bool started_;
  int numThreads_;//线程池中线程的数量
  int next_;
  std::vector<std::unique_ptr<EventLoopThread>> threads_;//线程保存在vector中
  std::vector<EventLoop*> loops_;//每个EventLoopThread线程对应的EventLoop保存在loops_中
};

}  // namespace net
}  // namespace muduo

#endif  // MUDUO_NET_EVENTLOOPTHREADPOOL_H

EventLoopThreadPool.cc

#include "muduo/net/EventLoopThreadPool.h"

#include "muduo/net/EventLoop.h"
#include "muduo/net/EventLoopThread.h"

#include <stdio.h>

using namespace muduo;
using namespace muduo::net;

EventLoopThreadPool::EventLoopThreadPool(EventLoop* baseLoop, const string& nameArg)
  : baseLoop_(baseLoop),
    name_(nameArg),
    started_(false),
    numThreads_(0),
    next_(0)
{
}

EventLoopThreadPool::~EventLoopThreadPool()
{
  // Don't delete loop, it's stack variable
}

/*
*  该函数会创建 numThreads_个EventLoopThread对象并运行各个线程
*  并在主线程保存创建的EventLoopThread对象和EventLoopThread线程创建的EventLoop对象。
*  如果创建的线程为0,则执行cb(baseLoop_);
*/
void EventLoopThreadPool::start(const ThreadInitCallback& cb)
{
  assert(!started_);
  baseLoop_->assertInLoopThread();

  started_ = true;

  for (int i = 0; i < numThreads_; ++i)
  {
    char buf[name_.size() + 32];
    snprintf(buf, sizeof buf, "%s%d", name_.c_str(), i);
    EventLoopThread* t = new EventLoopThread(cb, buf);//创建线程
    threads_.push_back(std::unique_ptr<EventLoopThread>(t));//保存线程
    loops_.push_back(t->startLoop());//保存线程对应的EventLoop对象
  }
  if (numThreads_ == 0 && cb)
  {
    cb(baseLoop_);
  }
}



//调用该函数会按照轮流的顺序返回池里的线程的EventLoop对象指针
EventLoop* EventLoopThreadPool::getNextLoop()
{
  baseLoop_->assertInLoopThread();
  assert(started_);
  EventLoop* loop = baseLoop_;

  if (!loops_.empty())
  {
    // round-robin
    loop = loops_[next_];
    ++next_;
    if (implicit_cast<size_t>(next_) >= loops_.size())
    {
      next_ = 0;
    }
  }
  return loop;
}

EventLoop* EventLoopThreadPool::getLoopForHash(size_t hashCode)
{
  baseLoop_->assertInLoopThread();
  EventLoop* loop = baseLoop_;

  if (!loops_.empty())
  {
    loop = loops_[hashCode % loops_.size()];
  }
  return loop;
}

std::vector<EventLoop*> EventLoopThreadPool::getAllLoops()
{
  baseLoop_->assertInLoopThread();
  assert(started_);
  if (loops_.empty())
  {
    return std::vector<EventLoop*>(1, baseLoop_);
  }
  else
  {
    return loops_;
  }
}

 void EventLoopThreadPool::setThreadNum(int numThreads)
  { 
     numThreads_ = numThreads; 
   }

EventLoopThreadPool类的使用:

EventLoop loop;
// 创建线程池
EventLoopThreadPool pool(&loop, "");
// 设置线程个数
poll.setThreadNum(100);
// 启动线程池
poll.start();

EventLoopThread

EventLoopThread类专门创建一个线程用于执行Reactor的事件循环,当然这只是一个辅助类,没有说一定要使用它,可以根据自己的情况进行选择,你也可以不创建线程去执行事件循环,而在主线程中执行事件循环,一切根据自己的需要。


EventLoopThread(也叫IO线程)的工作流程为

1、在主线程(暂且这么称呼)创建EventLoopThread对象。
2、主线程调用EventLoopThread.start(),启动EventLoopThread中的线程
(称为IO线程),此时主线程要等待IO线程创建完成EventLoop对象。
3、IO线程调用threadFunc创建EventLoop对象,通知主线程已经创建完成。
4、主线程返回创建的EventLoop对象。


EventLoopThread.h

#ifndef MUDUO_NET_EVENTLOOPTHREAD_H
#define MUDUO_NET_EVENTLOOPTHREAD_H

#include "muduo/base/Condition.h"
#include "muduo/base/Mutex.h"
#include "muduo/base/Thread.h"

namespace muduo
{
namespace net
{

class EventLoop;

class EventLoopThread : noncopyable
{
 public:
  typedef std::function<void(EventLoop*)> ThreadInitCallback;

  EventLoopThread(const ThreadInitCallback& cb = ThreadInitCallback(),
                  const string& name = string());
  ~EventLoopThread();
  EventLoop* startLoop();

 private:
  void threadFunc();

  EventLoop* loop_ GUARDED_BY(mutex_);
  bool exiting_;
  Thread thread_;
  MutexLock mutex_;
  Condition cond_ GUARDED_BY(mutex_);
  ThreadInitCallback callback_;
};

EventLoopThread.cc

#include "muduo/net/EventLoopThread.h"

#include "muduo/net/EventLoop.h"

using namespace muduo;
using namespace muduo::net;

EventLoopThread::EventLoopThread(const ThreadInitCallback& cb,
                                 const string& name)
  : loop_(NULL),
    exiting_(false),
    //将线程函数绑定为threadFunc
    thread_(std::bind(&EventLoopThread::threadFunc, this), name),
    mutex_(),
    cond_(mutex_),
    callback_(cb)
{
}

EventLoopThread::~EventLoopThread()
{
  exiting_ = true;
  if (loop_ != NULL) // not 100% race-free, eg. threadFunc could be running callback_.
  {
    // still a tiny chance to call destructed object, if threadFunc exits just now.
    // but when EventLoopThread destructs, usually programming is exiting anyway.
    loop_->quit();
    thread_.join();
  }
}

 //启动一个EventLoop线程
EventLoop* EventLoopThread::startLoop()
{
  assert(!thread_.started());
  /*
  * 在Thread::start中执行指定的线程函数,即EventLoopThread::threadFunc
  * 创建EventLoop对象
  */
  thread_.start();

  EventLoop* loop = NULL;
  {
    MutexLockGuard lock(mutex_);
    while (loop_ == NULL)
    {
      cond_.wait();// 等待创建好当前IO线程(EventLoop对象)
    }
    loop = loop_;
  }

  return loop;
}
/*
* threadFunc()运行的就是实例化一个EventLoop,并让这个EventLoop进入到loop状态。
* 运行EventLoopThread内的回调callback_
* 将刚定义好的loop传入这个回调
* 现在这个回调肯定是EventLoopThread的拥有者注册进去的
* 然后EventLoopThread也就有了一个EventLoop
* 使用loop_指向他,在这个线程中这个EventLoop一直出于loop()状态
*/
void EventLoopThread::threadFunc()
{
  EventLoop loop;

  if (callback_)
  {
    callback_(&loop);// 如果有初始化函数,就先调用初始化函数
  }

  {
    MutexLockGuard lock(mutex_);
    loop_ = &loop;
    cond_.notify();//通知startLoop线程已经启动完毕
  }

  loop.loop();  // 事件循环
  //assert(exiting_);
  MutexLockGuard lock(mutex_);
  loop_ = NULL;
}

EventLoop

  • one loop per thread意思是说每个线程最多只能有一个EventLoop对象。

  • EventLoop对象构造的时候,会检查当前线程是否已经创建了其他EventLoop对象,如果已创建,终止程序(LOG_FATAL)

  • EventLoop构造函数会记住本对象所属线程(threadId_)。

  • 创建了EventLoop对象的线程称为IO线程,其功能是运行事件循环(EventLoop::loop)
    Muduo网络库——EventLoopThreadPool、EventLoopThread、EventLoop浅析_第1张图片


白色部分是外部类,对外可见,灰色部分是内部类,对外不可见

  • EventLoop是事件循环的抽象,Poller是I/O复用的抽象,有两个派生类,分别封装poll和epoll,这个地方是muduo库唯一使用面向对象编程风格封装的

  • EventLoop与Poller的关系是组合,一个EventLoop包含一个Poller,并且Poller的生存期由EventLoop来控制,EventLoop是调用Poller的poll()函数实现的

  • Channel是对事件的注册与响应的封装,Channel的update()函数负责注册和更新I/O的可读、可写事件,Channel的handleEvent()成员函数对所发生的I/O事件进行处理。当调用update()函数注册和更新I/O的可读、可写事件的时候,又会调用到EventLoop的updateChannel(),从而又调用了Poller的updateChannel(),相当于将Channel注册到Poller中或者将fd_文件描述符的一些可读可写事件注册到Poller中。

  • 一个Eventloop包含多个Channel,也就是说可以用来捕捉多个通道的可读可写事件,两者之间是聚合关系,也就是说EventLoop不负责Channel的生存期的控制。Channel的生存期的控制由TcpConnection、Acceptor、Connector等等这些类来控制

  • Channel不拥有文件描述符,意思就是当Channel对象销毁的时候不关闭文件描述符。它和文件描述符(不是一个类)的关系是关联关系,一个Channel有一个FileDescriptor,一个EventLoop有多个FileDescriptor,用来监听多个FileDescriptor的相关事件,这个FileDescriptor是被套接字所拥有的,也就是说生存期由套接字来控制,当套接字销毁,文件描述符销毁,套接字类的析构函数调用close()

  • Channel是TcpConnection、Acceptor、Connector的成员,关系也是组合,生存期由这些类控制,Acceptor是对被动连接的抽象,它关注的是监听套接字的可读事件,监听套接字的可读事件由Channel来注册,然后调用handleEvent(),从而又调用了Acceptor中的handRead(),这就是基于对象的编程思想,回调了handRead(),而不是在Channel中包含一个handRead(),然后由Acceptor继承下来。

  • 一旦监听套接字的可读事件(注册由Channel来完成发生,就回调handRead()来响应。

  • Connector的handleWrite()和handleError也是由Channel来注册,Connector()是对主动连接的抽象。一旦被动连接或者主动连接建立之后,就会得到一个已连接套接字,

    已连接套接字的抽象就是TcpConnection,它会关注一些事件。

  • Acceptor的生存期由TcpServer来控制,Connector的生存期由TcpClient来控制。

  • TcpServer与TcpConnection的关系是聚合,一个TcpServer包含多TcpConnection,但是不控制TcpConnection的生存期,因为有可能客户端关闭了连接,TcpConnection对象也要跟着销毁,而TcpServer对象并不销毁,对于TcpClient也是同样的道理。


loop执行过程

EventLoop的loop()函数实际上是调用Poller的poll()函数,可能是PollPoller的poll()函数,也可能是EPollPoller的poll()函数,poll()之后就会返回一些通道activeChannels,也就是检测到通道的一些文件描述符产生了可读事件,然后调用这些activeChannels的handleEvent(),接着调用回调函数(调用比如TcpConnection或者Acceptor或者Connector里面的回调函数)

Muduo网络库——EventLoopThreadPool、EventLoopThread、EventLoop浅析_第2张图片


Muduo网络库——EventLoopThreadPool、EventLoopThread、EventLoop浅析_第3张图片

  • enableReading():关注可读事件,把通道注册到EventLoop,从而注册到EventLoop所持有的poll对象中

  • 能够poll之前,肯定要注册一些事件,注册事件是由enableReading()、enableWriting()发起的,

  • void enableReading() { events_ |= kReadEvent; update(); }
    它会调用Update(),进而又调用EventLoop的UpdateChannel(),进而调用Poller的UpdateChannel()

Muduo网络库——EventLoopThreadPool、EventLoopThread、EventLoop浅析_第4张图片


EventLoop.cc

//事件循环,不能跨线程调用
//只能在创建该对象的线程中调用
void EventLoop::loop()
{
  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_);  //调用poll返回活动的通道,有可能是唤醒返回的
    ++iteration_; 
    if (Logger::logLevel() <= Logger::TRACE)
    {
      printActiveChannels();  //日志登记,日志打印
    }
    // TODO sort channel by priority
    eventHandling_ = true;  //true
    for (ChannelList::iterator it = activeChannels_.begin();
        it != activeChannels_.end(); ++it)  //遍历通道来进行处理
    {
      currentActiveChannel_ = *it;
      currentActiveChannel_->handleEvent(pollReturnTime_);
    }
    currentActiveChannel_ = NULL;   //处理完了赋空
    eventHandling_ = false;  //false
 //I/O线程设计比较灵活,通过下面这个设计也能够进行计算任务,否则当I/O不是很繁忙的时候,这个I/O线程就一直处于阻塞状态。
 //我们需要让它也能执行一些计算任务 
    doPendingFunctors();   //处理用户回调任务
  }
 
  LOG_TRACE << "EventLoop " << this << " stop looping";
  looping_ = false;
}

你可能感兴趣的:(Muduo)